def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(alice, b, user_pk, user_sk): from bigchaindb.models import Transaction from bigchaindb.common.transaction import _fulfillment_to_details # CREATE divisible asset tx_create = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk, alice.public_key], 50)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) b.store_bulk_transactions([tx_create_signed]) assert tx_transfer_signed.validate(b) == tx_transfer_signed assert len(tx_transfer_signed.outputs) == 1 assert tx_transfer_signed.outputs[0].amount == 100 assert len(tx_transfer_signed.inputs) == 2 ffill_fid0 = _fulfillment_to_details(tx_transfer_signed.inputs[0].fulfillment) ffill_fid1 = _fulfillment_to_details(tx_transfer_signed.inputs[1].fulfillment) assert 'subconditions' not in ffill_fid0 assert 'subconditions' in ffill_fid1 assert len(ffill_fid1['subconditions']) == 2 b.store_bulk_transactions([tx_transfer_signed]) with pytest.raises(DoubleSpend): tx_transfer_signed.validate(b)
def test_amount_error_transfer(alice, b, user_pk, user_sk): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import AmountError # CREATE divisible asset tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) b.store_bulk_transactions([tx_create_signed]) # TRANSFER # output amount less than input amount tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AmountError): tx_transfer_signed.validate(b) # TRANSFER # output amount greater than input amount tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 101)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AmountError): tx_transfer_signed.validate(b)
def test_post_transaction_responses(tendermint_ws_url, b): from bigchaindb.common.crypto import generate_key_pair from bigchaindb.models import Transaction alice = generate_key_pair() bob = generate_key_pair() tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=None)\ .sign([alice.private_key]) code, message = b.write_transaction(tx, 'broadcast_tx_commit') assert code == 202 tx_transfer = Transaction.transfer(tx.to_inputs(), [([bob.public_key], 1)], asset_id=tx.id)\ .sign([alice.private_key]) code, message = b.write_transaction(tx_transfer, 'broadcast_tx_commit') assert code == 202 carly = generate_key_pair() double_spend = Transaction.transfer( tx.to_inputs(), [([carly.public_key], 1)], asset_id=tx.id, ).sign([alice.private_key]) for mode in ('broadcast_tx_sync', 'broadcast_tx_commit'): code, message = b.write_transaction(double_spend, mode) assert code == 500 assert message == 'Transaction validation failed'
def test_threshold_same_public_key(alice, b, user_pk, user_sk): # If we try to fulfill a threshold condition where each subcondition has # the same key get_subcondition_from_vk will always return the first # subcondition. This means that only the 1st subfulfillment will be # generated # Creating threshold conditions with the same key does not make sense but # that does not mean that the code shouldn't work. from bigchaindb.models import Transaction # CREATE divisible asset tx_create = Transaction.create([alice.public_key], [([user_pk, user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk, user_sk]) b.store_bulk_transactions([tx_create_signed]) assert tx_transfer_signed.validate(b) == tx_transfer_signed b.store_bulk_transactions([tx_transfer_signed]) with pytest.raises(DoubleSpend): tx_transfer_signed.validate(b)
def txns(b, user_pk, user_sk, user2_pk, user2_sk): txs = [Transaction.create([user_pk], [([user2_pk], 1)]).sign([user_sk]), Transaction.create([user2_pk], [([user_pk], 1)]).sign([user2_sk]), Transaction.create([user_pk], [([user_pk], 1), ([user2_pk], 1)]) .sign([user_sk])] b.store_bulk_transactions(txs) return txs
def test_get_spent_with_double_spend_detected(self, b, alice): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.exceptions import CriticalDoubleSpend tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)], asset_id=tx.id) transfer_tx = transfer_tx.sign([alice.private_key]) transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 2)], asset_id=tx.id) transfer_tx2 = transfer_tx2.sign([alice.private_key]) with pytest.raises(DoubleSpend): b.validate_transaction(transfer_tx2, [transfer_tx]) b.store_bulk_transactions([transfer_tx]) with pytest.raises(DoubleSpend): b.validate_transaction(transfer_tx2) b.store_bulk_transactions([transfer_tx2]) with pytest.raises(CriticalDoubleSpend): b.get_spent(tx.id, 0)
def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): from bigchaindb.models import Transaction tx_transfer = Transaction.transfer(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id) asset_id = Transaction.get_asset_id(tx_transfer) assert asset_id == tx_transfer.asset['id']
def test_get_spent_multiple_owners(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() transactions = [] for i in range(3): payload = {'somedata': i} tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)], payload) tx = tx.sign([alice.private_key]) transactions.append(tx) b.store_bulk_transactions(transactions) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) # check spents for input_tx in owned_inputs_user1: assert b.get_spent(input_tx.txid, input_tx.output) is None # create a transaction tx = Transaction.transfer(transactions[0].to_inputs(), [([user3_pk], 1)], asset_id=transactions[0].id) tx = tx.sign([user_sk, user2_sk]) b.store_bulk_transactions([tx]) # check that used inputs are marked as spent assert b.get_spent(transactions[0].id, 0) == tx # check that the other remain marked as unspent for unspent in transactions[1:]: assert b.get_spent(unspent.id, 0) is None
def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk): from bigchaindb import backend from bigchaindb.models import Transaction from bigchaindb.common.crypto import generate_key_pair from bigchaindb.common.exceptions import DoubleSpend alice = generate_key_pair() bob = generate_key_pair() tx1 = Transaction.create([user_pk], [([alice.public_key], 3), ([user_pk], 2)], asset=None)\ .sign([user_sk]) b.store_bulk_transactions([tx1]) inputs = tx1.to_inputs() tx2 = Transaction.transfer([inputs[1]], [([user2_pk], 2)], tx1.id).sign([user_sk]) assert tx2.validate(b) tx2_dict = tx2.to_dict() fulfills = tx2_dict['inputs'][0]['fulfills'] tx2_dict['inputs'][0]['fulfills'] = {'output_index': fulfills['output_index'], 'transaction_id': fulfills['transaction_id']} backend.query.store_transactions(b.connection, [tx2_dict]) tx3 = Transaction.transfer([inputs[1]], [([bob.public_key], 2)], tx1.id).sign([user_sk]) with pytest.raises(DoubleSpend): tx3.validate(b)
def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user_pk) expected_owned_inputs_user1 = [TransactionLink(tx.id, 0)] assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == expected_owned_inputs_user1 tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], asset_id=tx.id) tx = tx.sign([user_sk, user2_sk]) b.store_bulk_transactions([tx]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user2_pk) spent_user1 = b.get_spent(tx.id, 0) assert owned_inputs_user1 == owned_inputs_user2 assert not spent_user1
def test_get_spent_single_tx_single_output(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() tx = Transaction.create([alice.public_key], [([user_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk).pop() # check spents input_txid = owned_inputs_user1.txid spent_inputs_user1 = b.get_spent(input_txid, 0) assert spent_inputs_user1 is None # create a transaction and send it tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], asset_id=tx.id) tx = tx.sign([user_sk]) b.store_bulk_transactions([tx]) spent_inputs_user1 = b.get_spent(input_txid, 0) assert spent_inputs_user1 == tx
def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() # create divisible asset tx_create = Transaction.create([alice.public_key], [([user_pk], 1), ([user_pk], 1)]) tx_create_signed = tx_create.sign([alice.private_key]) b.store_bulk_transactions([tx_create_signed]) # get input owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user2_pk) expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0), TransactionLink(tx_create.id, 1)] assert owned_inputs_user1 == expected_owned_inputs_user1 assert owned_inputs_user2 == [] # transfer divisible asset divided in two outputs tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user2_pk], 1), ([user2_pk], 1)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) b.store_bulk_transactions([tx_transfer_signed]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user2_pk) assert owned_inputs_user1 == expected_owned_inputs_user1 assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0), TransactionLink(tx_transfer.id, 1)]
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() tx = Transaction.create([alice.public_key], [([user_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user2_pk) assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] tx_transfer = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], asset_id=tx.id) tx_transfer = tx_transfer.sign([user_sk]) b.store_bulk_transactions([tx_transfer]) owned_inputs_user1 = b.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.fastquery.get_outputs_by_public_key(user2_pk) assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0)]
def test_multiple_owners_before_multiple_owners_after_single_input(self, b, user_sk, user_pk, alice): from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() user4_sk, user4_pk = crypto.generate_key_pair() tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) # get input tx_link = b.fastquery.get_outputs_by_public_key(user_pk).pop() tx_input = b.get_transaction(tx_link.txid) tx = Transaction.transfer(tx_input.to_inputs(), [([user3_pk, user4_pk], 1)], asset_id=tx_input.id) tx = tx.sign([user_sk, user2_sk]) tx.validate(b) assert len(tx.inputs) == 1 assert len(tx.outputs) == 1
def test_single_in_single_own_multiple_out_mix_own_transfer(alice, b, user_pk, user_sk): from bigchaindb.models import Transaction # CREATE divisible asset tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 50), ([alice.public_key, alice.public_key], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) b.store_bulk_transactions([tx_create_signed]) assert tx_transfer_signed.validate(b) == tx_transfer_signed assert len(tx_transfer_signed.outputs) == 2 assert tx_transfer_signed.outputs[0].amount == 50 assert tx_transfer_signed.outputs[1].amount == 50 output_cid1 = tx_transfer_signed.outputs[1].to_dict() assert 'subconditions' in output_cid1['condition']['details'] assert len(output_cid1['condition']['details']['subconditions']) == 2 assert len(tx_transfer_signed.inputs) == 1 b.store_bulk_transactions([tx_transfer_signed]) with pytest.raises(DoubleSpend): tx_transfer_signed.validate(b)
def test_get_metadata_limit_tendermint(client, b, alice): from bigchaindb.models import Transaction # create two assets asset1 = {'msg': 'abc 1'} meta1 = {'key': 'meta 1'} tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)], metadata=meta1, asset=asset1).sign([alice.private_key]) b.store_bulk_transactions([tx1]) asset2 = {'msg': 'abc 2'} meta2 = {'key': 'meta 2'} tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)], metadata=meta2, asset=asset2).sign([alice.private_key]) b.store_bulk_transactions([tx2]) # test that both assets are returned without limit res = client.get(METADATA_ENDPOINT + '?search=meta') assert res.status_code == 200 assert len(res.json) == 2 # test that only one asset is returned when using limit=1 res = client.get(METADATA_ENDPOINT + '?search=meta&limit=1') assert res.status_code == 200 assert len(res.json) == 1
def test_get_divisble_transactions_returns_500(b, client): from bigchaindb.models import Transaction from bigchaindb.common import crypto import json TX_ENDPOINT = '/api/v1/transactions' def mine(tx_list): b.store_bulk_transactions(tx_list) alice_priv, alice_pub = crypto.generate_key_pair() bob_priv, bob_pub = crypto.generate_key_pair() carly_priv, carly_pub = crypto.generate_key_pair() create_tx = Transaction.create([alice_pub], [([alice_pub], 4)]) create_tx.sign([alice_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(create_tx.to_dict())) assert res.status_code == 202 mine([create_tx]) transfer_tx = Transaction.transfer(create_tx.to_inputs(), [([alice_pub], 3), ([bob_pub], 1)], asset_id=create_tx.id) transfer_tx.sign([alice_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) assert res.status_code == 202 mine([transfer_tx]) transfer_tx_carly = Transaction.transfer([transfer_tx.to_inputs()[1]], [([carly_pub], 1)], asset_id=create_tx.id) transfer_tx_carly.sign([bob_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx_carly.to_dict())) assert res.status_code == 202 mine([transfer_tx_carly]) asset_id = create_tx.id url = TX_ENDPOINT + '?asset_id=' + asset_id assert client.get(url).status_code == 200 assert len(client.get(url).json) == 3 url = OUTPUTS_ENDPOINT + '?public_key=' + alice_pub assert client.get(url).status_code == 200 url = OUTPUTS_ENDPOINT + '?public_key=' + bob_pub assert client.get(url).status_code == 200 url = OUTPUTS_ENDPOINT + '?public_key=' + carly_pub assert client.get(url).status_code == 200
def generate_create_and_transfer(keypair=None): if not keypair: keypair = generate_key_pair() priv_key, pub_key = keypair create_tx = Transaction.create([pub_key], [([pub_key], 10)]).sign([priv_key]) transfer_tx = Transaction.transfer( create_tx.to_inputs(), [([pub_key], 10)], asset_id=create_tx.id).sign([priv_key]) return create_tx, transfer_tx
def test_asset_id_mismatch(alice, user_pk): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import AssetIdMismatch tx1 = Transaction.create([alice.public_key], [([user_pk], 1)], metadata={'msg': random.random()}) tx1.sign([alice.private_key]) tx2 = Transaction.create([alice.public_key], [([user_pk], 1)], metadata={'msg': random.random()}) tx2.sign([alice.private_key]) with pytest.raises(AssetIdMismatch): Transaction.get_asset_id([tx1, tx2])
def test_get_spent_issue_1271(b, alice, bob, carol): from bigchaindb.models import Transaction tx_1 = Transaction.create( [carol.public_key], [([carol.public_key], 8)], ).sign([carol.private_key]) assert tx_1.validate(b) b.store_bulk_transactions([tx_1]) tx_2 = Transaction.transfer( tx_1.to_inputs(), [([bob.public_key], 2), ([alice.public_key], 2), ([carol.public_key], 4)], asset_id=tx_1.id, ).sign([carol.private_key]) assert tx_2.validate(b) b.store_bulk_transactions([tx_2]) tx_3 = Transaction.transfer( tx_2.to_inputs()[2:3], [([alice.public_key], 1), ([carol.public_key], 3)], asset_id=tx_1.id, ).sign([carol.private_key]) assert tx_3.validate(b) b.store_bulk_transactions([tx_3]) tx_4 = Transaction.transfer( tx_2.to_inputs()[1:2] + tx_3.to_inputs()[0:1], [([bob.public_key], 3)], asset_id=tx_1.id, ).sign([alice.private_key]) assert tx_4.validate(b) b.store_bulk_transactions([tx_4]) tx_5 = Transaction.transfer( tx_2.to_inputs()[0:1], [([alice.public_key], 2)], asset_id=tx_1.id, ).sign([bob.private_key]) assert tx_5.validate(b) b.store_bulk_transactions([tx_5]) assert b.get_spent(tx_2.id, 0) == tx_5 assert not b.get_spent(tx_5.id, 0) assert b.get_outputs_filtered(alice.public_key) assert b.get_outputs_filtered(alice.public_key, spent=False)
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included try: tx.validate(self.bigchain) return tx except ValidationError as e: logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None
def test_deliver_tx__double_spend_fails(b, init_chain_request): from bigchaindb import App from bigchaindb.models import Transaction from bigchaindb.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() tx = Transaction.create([alice.public_key], [([bob.public_key], 1)])\ .sign([alice.private_key]) app = App(b) app.init_chain(init_chain_request) begin_block = RequestBeginBlock() app.begin_block(begin_block) result = app.deliver_tx(encode_tx_to_bytes(tx)) assert result.code == CodeTypeOk app.end_block(RequestEndBlock(height=99)) app.commit() assert b.get_transaction(tx.id).id == tx.id result = app.deliver_tx(encode_tx_to_bytes(tx)) assert result.code == CodeTypeError
def test_post_create_transaction_with_language(b, client, nested, language, expected_status_code): from bigchaindb.models import Transaction from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection if isinstance(b.connection, LocalMongoDBConnection): user_priv, user_pub = crypto.generate_key_pair() lang_obj = {'language': language} if nested: asset = {'root': lang_obj} else: asset = lang_obj tx = Transaction.create([user_pub], [([user_pub], 1)], asset=asset) tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) assert res.status_code == expected_status_code if res.status_code == 400: expected_error_message = ( 'Invalid transaction (ValidationError): MongoDB does not support ' 'text search for the language "{}". If you do not understand this ' 'error message then please rename key/field "language" to something ' 'else like "lang".').format(language) assert res.json['message'] == expected_error_message
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] monitor = current_app.config['monitor'] # `force` will try to format the body of the POST request even if the `content-type` header is not # set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except (ValidationError, InvalidSignature): return make_error(400, 'Invalid transaction') with pool() as bigchain: if bigchain.is_valid_transaction(tx_obj): rate = bigchaindb.config['statsd']['rate'] with monitor.timer('write_transaction', rate=rate): bigchain.write_transaction(tx_obj) else: return make_error(400, 'Invalid transaction') return tx
def get_spent(self, txid, output, current_transactions=[]): transactions = backend.query.get_spent(self.connection, txid, output) transactions = list(transactions) if transactions else [] if len(transactions) > 1: raise core_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) current_spent_transactions = [] for ctxn in current_transactions: for ctxn_input in ctxn.inputs: if ctxn_input.fulfills and\ ctxn_input.fulfills.txid == txid and\ ctxn_input.fulfills.output == output: current_spent_transactions.append(ctxn) transaction = None if len(transactions) + len(current_spent_transactions) > 1: raise DoubleSpend('tx "{}" spends inputs twice'.format(txid)) elif transactions: transaction = Transaction.from_db(self, transactions[0]) elif current_spent_transactions: transaction = current_spent_transactions[0] return transaction
def test_websocket_block_event(b, test_client, loop): from bigchaindb import events from bigchaindb.web.websocket_server import init_app, POISON_PILL, EVENTS_ENDPOINT from bigchaindb.models import Transaction from bigchaindb.common import crypto user_priv, user_pub = crypto.generate_key_pair() tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) event_source = asyncio.Queue(loop=loop) app = init_app(event_source, loop=loop) client = yield from test_client(app) ws = yield from client.ws_connect(EVENTS_ENDPOINT) block = {'height': 1, 'transactions': [tx.to_dict()]} block_event = events.Event(events.EventTypes.BLOCK_VALID, block) yield from event_source.put(block_event) for tx in block['transactions']: result = yield from ws.receive() json_result = json.loads(result.data) assert json_result['transaction_id'] == tx['id'] # Since the transactions are all CREATEs, asset id == transaction id assert json_result['asset_id'] == tx['id'] assert json_result['height'] == block['height'] yield from event_source.put(POISON_PILL)
def get_block(self, block_id): """Get the block with the specified `block_id`. Returns the block corresponding to `block_id` or None if no match is found. Args: block_id (int): block id of the block to get. """ block = backend.query.get_block(self.connection, block_id) latest_block = self.get_latest_block() latest_block_height = latest_block['height'] if latest_block else 0 if not block and block_id > latest_block_height: return result = {'height': block_id, 'transactions': []} if block: transactions = backend.query.get_transactions(self.connection, block['transactions']) result['transactions'] = [t.to_dict() for t in Transaction.from_db(self, transactions)] return result
def test_post_create_transaction_with_invalid_id(mock_logger, b, client): from bigchaindb.common.exceptions import InvalidHash from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() tx['id'] = 'abcd' * 16 res = client.post(TX_ENDPOINT, data=json.dumps(tx)) expected_status_code = 400 expected_error_message = ( "Invalid transaction ({}): The transaction's id '{}' isn't equal to " "the hash of its body, i.e. it's not valid." ).format(InvalidHash.__name__, tx['id']) assert res.status_code == expected_status_code assert res.json['message'] == expected_error_message assert mock_logger.error.called assert ( 'HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s' in mock_logger.error.call_args[0] ) assert ( { 'message': expected_error_message, 'status': expected_status_code, 'method': 'POST', 'path': TX_ENDPOINT } in mock_logger.error.call_args[0] )
def validate_tx(self, tx_dict, block_id, num_tx): """Validate a transaction. Transaction must also not be in any VALID block. Args: tx_dict (dict): the transaction to validate block_id (str): the id of block containing the transaction num_tx (int): the total number of transactions to process Returns: Three values are returned, the validity of the transaction, ``block_id``, ``num_tx``. """ try: tx = Transaction.from_dict(tx_dict) new = self.bigchain.is_new_transaction(tx.id, exclude_block_id=block_id) if not new: raise exceptions.ValidationError('Tx already exists, %s', tx.id) tx.validate(self.bigchain) valid = True except exceptions.ValidationError as e: valid = False logger.warning('Invalid tx: %s', e) return valid, block_id, num_tx
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ tx = Transaction.from_dict(tx) if self.bigchain.transaction_exists(tx.id): # if the transaction already exists, we must check whether # it's in a valid or undecided block tx, status = self.bigchain.get_transaction(tx.id, include_status=True) if status == self.bigchain.TX_VALID \ or status == self.bigchain.TX_UNDECIDED: # if the tx is already in a valid or undecided block, # then it no longer should be in the backlog, or added # to a new block. We can delete and drop it. self.bigchain.delete_transaction(tx.id) return None tx_validated = self.bigchain.is_valid_transaction(tx) if tx_validated: return tx else: # if the transaction is not valid, remove it from the # backlog self.bigchain.delete_transaction(tx.id) return None
def test_single_in_single_own_multiple_out_mix_own_create(alice, user_pk, b): from bigchaindb.models import Transaction tx = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk, user_pk], 50)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key]) assert tx_signed.validate(b) == tx_signed assert len(tx_signed.outputs) == 2 assert tx_signed.outputs[0].amount == 50 assert tx_signed.outputs[1].amount == 50 output_cid1 = tx_signed.outputs[1].to_dict() assert 'subconditions' in output_cid1['condition']['details'] assert len(output_cid1['condition']['details']['subconditions']) == 2 assert len(tx_signed.inputs) == 1
def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import InvalidSignature user_pub = crypto.generate_key_pair()[1] input_valid = b.get_owned_ids(user_pk).pop() create_tx = b.get_transaction(input_valid.txid) transfer_tx = Transaction.transfer(create_tx.to_inputs(), [([user_pub], 1)], asset_id=create_tx.id) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) expected_status_code = 400 expected_error_message = 'Invalid transaction ({}): {}'.format( InvalidSignature.__name__, 'Transaction signature is invalid.') assert res.status_code == expected_status_code assert res.json['message'] == expected_error_message
def __init__(self): """Initialize the Block voter.""" # Since cannot share a connection to RethinkDB using multiprocessing, # we need to create a temporary instance of BigchainDB that we use # only to query RethinkDB # This is the Bigchain instance that will be "shared" (aka: copied) # by all the subprocesses self.bigchain = Bigchain() self.last_voted_id = Bigchain().get_last_voted_block().id self.counters = Counter() self.validity = {} self.invalid_dummy_tx = Transaction.create([self.bigchain.me], [([self.bigchain.me], 1)])
def validate_transaction(self, tx, current_transactions=[]): """Validate a transaction against the current status of the database.""" transaction = tx # CLEANUP: The conditional below checks for transaction in dict format. # It would be better to only have a single format for the transaction # throught the code base. if isinstance(transaction, dict): try: transaction = Transaction.from_dict(tx) except SchemaValidationError as e: logger.warning('Invalid transaction schema: %s', e.__cause__.message) return False except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False return transaction.validate(self, current_transactions)
def test_read_transaction(self, b, user_pk, user_sk): from bigchaindb.models import Transaction input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() tx = Transaction.transfer(inputs, [([user_pk], 1)], asset_id=input_tx.id) tx = tx.sign([user_sk]) b.write_transaction(tx) # create block and write it to the bighcain before retrieving the transaction block = b.create_block([tx]) b.write_block(block) response, status = b.get_transaction(tx.id, include_status=True) # add validity information, which will be returned assert tx.to_dict() == response.to_dict()
def post(self): pool = current_app.config['bigchain_pool'] tx = request.get_json(force=True) tx_obj = Transaction.from_dict(tx) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except (ValueError, OperationError, TransactionDoesNotExist, TransactionOwnerError, FulfillmentNotInValidBlock, DoubleSpend, InvalidHash, InvalidSignature, AmountError) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) else: bigchain.write_transaction(tx_obj) return tx, 202
def test_write_transaction(self, b, user_sk, user_pk, alice, create_tx): from bigchaindb.models import Transaction asset1 = {'msg': 'BigchainDB 1'} tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=asset1).sign([alice.private_key]) b.store_bulk_transactions([tx]) tx_from_db = b.get_transaction(tx.id) before = tx.to_dict() after = tx_from_db.to_dict() assert before['asset']['data'] == after['asset']['data'] before.pop('asset', None) after.pop('asset', None) assert before == after
def test_single_owner_before_multiple_owners_after_single_input( self, b, user_sk, user_pk, inputs): from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() tx_link = b.fastquery.get_outputs_by_public_key(user_pk).pop() input_tx = b.get_transaction(tx_link.txid) tx = Transaction.transfer(input_tx.to_inputs(), [([user2_pk, user3_pk], 1)], asset_id=input_tx.id) tx = tx.sign([user_sk]) tx.validate(b) assert len(tx.inputs) == 1 assert len(tx.outputs) == 1
def inputs_shared(user_pk, user2_pk, alice): from bigchaindb.models import Transaction # create blocks with transactions for `USER` to spend for block in range(4): transactions = [ Transaction.create( [alice.public_key], [user_pk, user2_pk], metadata={ 'msg': random.random() }, ).sign([alice.private_key]).to_dict() for _ in range(10) ] block = Block(app_hash='', height=_get_height(b), transaction=transactions) b.store_block(block._asdict())
def get_tx_by_metadata_id(self, metadata_id): """Retrieves transactions related to a metadata. When creating a transaction one of the optional arguments is the `metadata`. The metadata is a generic dict that contains extra information that can be appended to the transaction. To make it easy to query the bigchain for that particular metadata we create a UUID for the metadata and store it with the transaction. Args: metadata_id (str): the id for this particular metadata. Returns: A list of transactions containing that metadata. If no transaction exists with that metadata it returns an empty list `[]` """ cursor = self.backend.get_transactions_by_metadata_id(metadata_id) return [Transaction.from_dict(tx) for tx in cursor]
def test_post_transaction_valid_modes(mock_post, client, mode): from bigchaindb.models import Transaction from bigchaindb.common.crypto import generate_key_pair def _mock_post(*args, **kwargs): return Mock(json=Mock(return_value={'result': {'code': 0}})) mock_post.side_effect = _mock_post alice = generate_key_pair() tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]) mode_endpoint = TX_ENDPOINT + mode[0] client.post(mode_endpoint, data=json.dumps(tx.to_dict())) args, kwargs = mock_post.call_args assert mode[1] == kwargs['json']['method']
def test_double_create(b, user_pk): from bigchaindb.models import Transaction from bigchaindb.backend.query import count_blocks tx = Transaction.create([b.me], [([user_pk], 1)], metadata={'test': 'test'}).sign([b.me_private]) b.write_transaction(tx) time.sleep(5) b.write_transaction(tx) time.sleep(5) tx_returned = b.get_transaction(tx.id) # test that the tx can be queried assert tx_returned == tx # test the transaction appears only once last_voted_block = b.get_last_voted_block() assert len(last_voted_block.transactions) == 1 assert count_blocks(b.connection) == 2
def get_spent(self, txid, cid): """Check if a `txid` was already used as an input. A transaction can be used as an input for another transaction. Bigchain needs to make sure that a given `txid` is only used once. Args: txid (str): The id of the transaction cid (num): the index of the condition in the respective transaction Returns: The transaction (Transaction) that used the `txid` as an input else `None` """ # checks if an input was already spent # checks if the bigchain has any transaction with input {'txid': ..., 'cid': ...} response = self.connection.run( r.table('bigchain', read_mode=self.read_mode) .concat_map(lambda doc: doc['block']['transactions']) .filter(lambda transaction: transaction['transaction']['fulfillments'] .contains(lambda fulfillment: fulfillment['input'] == {'txid': txid, 'cid': cid}))) transactions = list(response) # a transaction_id should have been spent at most one time if transactions: # determine if these valid transactions appear in more than one valid block num_valid_transactions = 0 for transaction in transactions: # ignore invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: raise exceptions.DoubleSpend('`{}` was spent more then once. There is a problem with the chain'.format( txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) else: # all queried transactions were invalid return None else: return None
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ parser = reqparse.RequestParser() parser.add_argument('mode', type=parameters.valid_mode, default='broadcast_tx_async') args = parser.parse_args() mode = str(args['mode']) pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error(400, message='Invalid transaction schema: {}'.format( e.__cause__.message)) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) else: bigchain.write_transaction(tx_obj, mode) response = jsonify(tx) response.status_code = 202 return response
def test_validation_worker_process_multiple_transactions(b): import multiprocessing as mp from bigchaindb.parallel_validation import ValidationWorker, RESET, EXIT keypair = generate_key_pair() create_tx, transfer_tx = generate_create_and_transfer(keypair) double_spend = Transaction.transfer( create_tx.to_inputs(), [([keypair.public_key], 10)], asset_id=create_tx.id).sign([keypair.private_key]) in_queue, results_queue = mp.Queue(), mp.Queue() vw = ValidationWorker(in_queue, results_queue) # Note: in the following instructions, the worker will encounter two # `RESET` messages, and an `EXIT` message. When a worker processes a # `RESET` message, it forgets all transactions it has validated. This allow # us to re-validate the same transactions. This won't happen in real life, # but it's quite handy to check if the worker actually forgot about the # past transactions (if not, it will return `False` because the # transactions look like a double spend). # `EXIT` makes the worker to stop the infinite loop. in_queue.put((0, create_tx.to_dict())) in_queue.put((10, transfer_tx.to_dict())) in_queue.put((20, double_spend.to_dict())) in_queue.put(RESET) in_queue.put((0, create_tx.to_dict())) in_queue.put((5, transfer_tx.to_dict())) in_queue.put(RESET) in_queue.put((20, create_tx.to_dict())) in_queue.put((25, double_spend.to_dict())) in_queue.put((30, transfer_tx.to_dict())) in_queue.put(EXIT) vw.run() assert results_queue.get() == (0, create_tx) assert results_queue.get() == (10, transfer_tx) assert results_queue.get() == (20, False) assert results_queue.get() == (0, create_tx) assert results_queue.get() == (5, transfer_tx) assert results_queue.get() == (20, create_tx) assert results_queue.get() == (25, double_spend) assert results_queue.get() == (30, False)
def test_integration_from_webapi_to_websocket(monkeypatch, client, loop): # XXX: I think that the `pytest-aiohttp` plugin is sparkling too much # magic in the `asyncio` module: running this test without monkey-patching # `asycio.get_event_loop` (and without the `loop` fixture) raises a: # RuntimeError: There is no current event loop in thread 'MainThread'. # # That's pretty weird because this test doesn't use the pytest-aiohttp # plugin explicitely. monkeypatch.setattr('asyncio.get_event_loop', lambda: loop) import json import random import aiohttp from bigchaindb.common import crypto from bigchaindb import processes from bigchaindb.models import Transaction # Start BigchainDB processes.start() loop = asyncio.get_event_loop() import time time.sleep(1) ws_url = client.get( 'http://localhost:9984/api/v1/').json['_links']['streams_v1'] # Connect to the WebSocket endpoint session = aiohttp.ClientSession() ws = loop.run_until_complete(session.ws_connect(ws_url)) # Create a keypair and generate a new asset user_priv, user_pub = crypto.generate_key_pair() asset = {'random': random.random()} tx = Transaction.create([user_pub], [([user_pub], 1)], asset=asset) tx = tx.sign([user_priv]) # Post the transaction to the BigchainDB Web API client.post('/api/v1/transactions/', data=json.dumps(tx.to_dict())) result = loop.run_until_complete(ws.receive()) json_result = json.loads(result.data) assert json_result['transaction_id'] == tx.id
def test_valid_block_voting_with_create_transaction(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.models import Transaction from bigchaindb.pipelines import vote # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() tx = Transaction.create([b.me], [([test_user_pub], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1111111111) block = b.create_block([tx]) block_dict = decouple_assets(b, block) inpipe = Pipe() outpipe = Pipe() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) inpipe.put(block_dict) vote_pipeline.start() vote_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block.id, b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block.id, 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk): sk, pk = crypto.generate_key_pair() from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() input_valid = b.get_owned_ids(user_pk).pop() create_tx = b.get_transaction(input_valid.txid) transfer_tx = Transaction.transfer(create_tx.to_inputs(), [([user_pub], 1)], asset_id=create_tx.id) transfer_tx = transfer_tx.sign([user_sk]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) assert res.status_code == 202 assert res.json['inputs'][0]['owners_before'][0] == user_pk assert res.json['outputs'][0]['public_keys'][0] == user_pub
def get_transaction(self, transaction_id): transaction = backend.query.get_transaction(self.connection, transaction_id) if transaction: asset = backend.query.get_asset(self.connection, transaction_id) metadata = backend.query.get_metadata(self.connection, [transaction_id]) if asset: transaction['asset'] = asset if 'metadata' not in transaction: metadata = metadata[0] if metadata else None if metadata: metadata = metadata.get('metadata') transaction.update({'metadata': metadata}) transaction = Transaction.from_dict(transaction) return transaction
def test_write_block(b, user_pk): from bigchaindb.models import Block, Transaction from bigchaindb.pipelines.block import BlockPipeline block_maker = BlockPipeline() txs = [] for _ in range(100): tx = Transaction.create([b.me], [([user_pk], 1)], metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) txs.append(tx) block_doc = b.create_block(txs) block_maker.write(block_doc) expected = b.get_block(block_doc.id) expected = Block.from_dict(expected) assert expected == block_doc
def test_write_and_post_transaction(mock_post, b): from bigchaindb.models import Transaction from bigchaindb.common.crypto import generate_key_pair from bigchaindb.tendermint_utils import encode_transaction alice = generate_key_pair() tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=None)\ .sign([alice.private_key]).to_dict() tx = b.validate_transaction(tx) b.write_transaction(tx, BROADCAST_TX_ASYNC) assert mock_post.called args, kwargs = mock_post.call_args assert BROADCAST_TX_ASYNC == kwargs['json']['method'] encoded_tx = [encode_transaction(tx.to_dict())] assert encoded_tx == kwargs['json']['params']
def inputs(user_pk, b, alice): from bigchaindb.models import Transaction # create blocks with transactions for `USER` to spend for height in range(1, 4): transactions = [ Transaction.create( [alice.public_key], [([user_pk], 1)], metadata={ 'msg': random.random() }, ).sign([alice.private_key]) for _ in range(10) ] tx_ids = [tx.id for tx in transactions] block = Block(app_hash='hash' + str(height), height=height, transactions=tx_ids) b.store_block(block._asdict()) b.store_bulk_transactions(transactions)
def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request): from bigchaindb import App from bigchaindb.backend import query from bigchaindb.models import Transaction from bigchaindb.backend.query import PRE_COMMIT_ID tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset={'msg': 'live long and prosper'})\ .sign([alice.private_key]) app = App(b) app.init_chain(init_chain_request) begin_block = RequestBeginBlock() app.begin_block(begin_block) app.deliver_tx(encode_tx_to_bytes(tx)) app.end_block(RequestEndBlock(height=99)) resp = query.get_pre_commit_state(b.connection, PRE_COMMIT_ID) assert resp['commit_id'] == PRE_COMMIT_ID assert resp['height'] == 99 assert resp['transactions'] == [tx.id] app.begin_block(begin_block) app.deliver_tx(encode_tx_to_bytes(tx)) app.end_block(RequestEndBlock(height=100)) resp = query.get_pre_commit_state(b.connection, PRE_COMMIT_ID) assert resp['commit_id'] == PRE_COMMIT_ID assert resp['height'] == 100 assert resp['transactions'] == [tx.id] # simulate a chain migration and assert the height is shifted b.store_abci_chain(100, 'new-chain') app = App(b) app.begin_block(begin_block) app.deliver_tx(encode_tx_to_bytes(tx)) app.end_block(RequestEndBlock(height=1)) resp = query.get_pre_commit_state(b.connection, PRE_COMMIT_ID) assert resp['commit_id'] == PRE_COMMIT_ID assert resp['height'] == 101 assert resp['transactions'] == [tx.id]
def get_spent(self, txid, output): """Check if a `txid` was already used as an input. A transaction can be used as an input for another transaction. Bigchain needs to make sure that a given `txid` is only used once. Args: txid (str): The id of the transaction output (num): the index of the output in the respective transaction Returns: The transaction (Transaction) that used the `txid` as an input else `None` """ # checks if an input was already spent # checks if the bigchain has any transaction with input {'txid': ..., # 'output': ...} transactions = list( backend.query.get_spent(self.connection, txid, output)) # a transaction_id should have been spent at most one time if transactions: # determine if these valid transactions appear in more than one valid block num_valid_transactions = 0 for transaction in transactions: # ignore invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: raise exceptions.DoubleSpend( ('`{}` was spent more than' ' once. There is a problem' ' with the chain').format(txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) else: # all queried transactions were invalid return None else: return None
def validate_transaction(self, tx, current_transactions=[]): """Validate a transaction against the current status of the database.""" transaction = tx if not isinstance(transaction, Transaction): try: transaction = Transaction.from_dict(tx) except SchemaValidationError as e: logger.warning('Invalid transaction schema: %s', e.__cause__.message) return False except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False try: return transaction.validate(self, current_transactions) except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False return transaction
def test_get_block_endpoint(b, client, alice): import copy tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}) tx = tx.sign([alice.private_key]) # with store_bulk_transactions we use `insert_many` where PyMongo # automatically adds an `_id` field to the tx, therefore we need the # deepcopy, for more info see: # https://api.mongodb.com/python/current/faq.html#writes-and-ids tx_dict = copy.deepcopy(tx.to_dict()) b.store_bulk_transactions([tx]) block = Block(app_hash='random_utxo', height=31, transactions=[tx.id]) b.store_block(block._asdict()) res = client.get(BLOCKS_ENDPOINT + str(block.height)) expected_response = {'height': block.height, 'transactions': [tx_dict]} assert res.json == expected_response assert res.status_code == 200
def test_get_assets_tendermint(client, b, alice): from bigchaindb.models import Transaction # test returns empty list when no assets are found res = client.get(ASSETS_ENDPOINT + '?search=abc') assert res.json == [] assert res.status_code == 200 # create asset asset = {'msg': 'abc'} tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=asset).sign([alice.private_key]) b.store_bulk_transactions([tx]) # test that asset is returned res = client.get(ASSETS_ENDPOINT + '?search=abc') assert res.status_code == 200 assert len(res.json) == 1 assert res.json[0] == {'data': {'msg': 'abc'}, 'id': tx.id}
def get_txs_by_asset_id(self, asset_id): """Retrieves transactions related to a particular asset. A digital asset in bigchaindb is identified by an uuid. This allows us to query all the transactions related to a particular digital asset, knowing the id. Args: asset_id (str): the id for this particular metadata. Returns: A list of transactions containing related to the asset. If no transaction exists for that asset it returns an empty list `[]` """ cursor = self.connection.run( r.table('bigchain', read_mode=self.read_mode) .get_all(asset_id, index='asset_id') .concat_map(lambda block: block['block']['transactions']) .filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id)) return [Transaction.from_dict(tx) for tx in cursor]
def test_full_pipeline(b, user_pk): from bigchaindb.models import Block, Transaction from bigchaindb.pipelines.block import create_pipeline outpipe = Pipe() pipeline = create_pipeline() pipeline.setup(outdata=outpipe) inpipe = pipeline.items[0] # include myself here, so that some tx are actually assigned to me b.nodes_except_me = [b.me, 'aaa', 'bbb', 'ccc'] number_assigned_to_others = 0 for i in range(100): tx = Transaction.create([b.me], [([user_pk], 1)], metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) tx = tx.to_dict() # simulate write_transaction tx['assignee'] = random.choice(b.nodes_except_me) if tx['assignee'] != b.me: number_assigned_to_others += 1 tx['assignment_timestamp'] = time.time() inpipe.put(tx) assert inpipe.qsize() == 100 pipeline.start() time.sleep(2) pipeline.terminate() block_doc = outpipe.get() chained_block = b.get_block(block_doc.id) chained_block = Block.from_dict(chained_block) block_len = len(block_doc.transactions) assert chained_block == block_doc assert number_assigned_to_others == 100 - block_len
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included try: # Do not allow an externally submitted GENESIS transaction. # A simple check is enough as a pipeline is started only after the # creation of GENESIS block, or after the verification of a GENESIS # block. Voting will fail at a later stage if the GENESIS block is # absent. if tx.operation == Transaction.GENESIS: raise GenesisBlockAlreadyExistsError( 'Duplicate GENESIS transaction') tx.validate(self.bigchain) return tx except ValidationError as e: logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None