diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index fb12b5b6..468ee649 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -99,6 +99,14 @@ class Fulfillment(object): ffill['fid'] = fid return ffill + @classmethod + def generate(cls, owners_before): + # TODO: write docstring + + if len(owners_before) == 1: + ffill = Ed25519Fulfillment(public_key=owners_before[0]) + return cls(ffill, owners_before) + @classmethod def from_dict(cls, ffill): """Transforms a Python dictionary to a Fulfillment object. @@ -269,7 +277,8 @@ class Condition(object): return cond @classmethod - def generate(cls, owners_after, amount=1): + def generate(cls, owners_after, amount): + # TODO: Update docstring """Generates a Condition from a specifically formed tuple or list. Note: @@ -703,7 +712,8 @@ class Transaction(object): @classmethod def create(cls, owners_before, owners_after, metadata=None, asset=None, - secret=None, time_expire=None, amount=1): + secret=None, time_expire=None): + # TODO: Update docstring """A simple way to generate a `CREATE` transaction. Note: @@ -738,10 +748,24 @@ class Transaction(object): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - if not isinstance(amount, int): - raise TypeError('`amount` must be a int') metadata = Metadata(metadata) + + ffils = [] + conds = [] + + # generate_conditions + for owner_after in owners_after: + pub_keys, amount = owner_after + conds.append(Condition.generate(pub_keys, amount)) + + # generate fulfillments + ffils.append(Fulfillment.generate(owners_before)) + + return cls(cls.CREATE, asset, ffils, conds, metadata) + + + if len(owners_before) == len(owners_after) and len(owners_after) == 1: # NOTE: Standard case, one owner before, one after. # NOTE: For this case its sufficient to use the same @@ -755,7 +779,7 @@ class Transaction(object): ffills = [Fulfillment(Ed25519Fulfillment(public_key=owner_before), [owner_before]) for owner_before in owners_before] - conds = [Condition.generate([owners], amount=amount) + conds = [Condition.generate([owners], amount) for owners in owners_after] return cls(cls.CREATE, asset, ffills, conds, metadata) @@ -1138,14 +1162,15 @@ class Transaction(object): tx_serialized, input_condition_uri) - if not fulfillments_count == conditions_count == \ - input_condition_uris_count: - raise ValueError('Fulfillments, conditions and ' - 'input_condition_uris must have the same count') - else: - partial_transactions = map(gen_tx, self.fulfillments, - self.conditions, input_condition_uris) - return all(partial_transactions) + # TODO: Why?? Need to ask @TimDaub + # if not fulfillments_count == conditions_count == \ + # input_condition_uris_count: + # raise ValueError('Fulfillments, conditions and ' + # 'input_condition_uris must have the same count') + # else: + partial_transactions = map(gen_tx, self.fulfillments, + self.conditions, input_condition_uris) + return all(partial_transactions) @staticmethod def _fulfillment_valid(fulfillment, operation, tx_serialized, diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py new file mode 100644 index 00000000..a52c4e19 --- /dev/null +++ b/tests/assets/test_divisible_assets.py @@ -0,0 +1,109 @@ +import pytest + + +# CREATE divisible asset +# Single input +# Single owners_before +# Single output +# single owners_after +def test_single_in_single_own_single_out_single_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 1 + assert tx_signed.conditions[0].amount == 100 + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single onwers_before +# Multiple outputs +# Single owners_after per output +def test_single_in_single_own_multiple_out_single_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)], asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 2 + assert tx_signed.conditions[0].amount == 50 + assert tx_signed.conditions[1].amount == 50 + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single owners_before +# Single output +# Multiple owners_after +def test_single_in_single_own_single_out_multiple_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk, user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 1 + assert tx_signed.conditions[0].amount == 100 + + condition = tx_signed.conditions[0].to_dict() + assert 'subfulfillments' in condition['condition']['details'] + assert len(condition['condition']['details']['subfulfillments']) == 2 + + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single owners_before +# Multiple outputs +# Mix: one output with a single owners_after, one output with multiple +# owners_after +def test_single_in_single_own_multiple_out_mix_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], + [([user_vk], 50), ([user_vk, user_vk], 50)], + asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 2 + assert tx_signed.conditions[0].amount == 50 + assert tx_signed.conditions[1].amount == 50 + + condition_cid1 = tx_signed.conditions[1].to_dict() + assert 'subfulfillments' in condition_cid1['condition']['details'] + assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 + + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Multiple owners_before +# Ouput combinations already tested above +# TODO: Support multiple owners_before in CREATE transactions +@pytest.mark.skip(reason=('CREATE transaction do not support multiple' + ' owners_before')) +def test_single_in_multiple_own_single_out_single_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me, b.me], [([user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me, b.me]) + assert tx_signed.validate(b) == tx_signed