Skip to content

Commit

Permalink
One-way communication offline funds transfer using QR-code (#3358)
Browse files Browse the repository at this point in the history
One-way communication offline funds transfer using QR-code
  • Loading branch information
mitchellolsthoorn authored and devos50 committed Jan 25, 2018
1 parent af35a1f commit 0d9041a
Show file tree
Hide file tree
Showing 7 changed files with 522 additions and 13 deletions.
70 changes: 69 additions & 1 deletion Tribler/Core/Modules/restapi/trustchain_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ class TrustchainEndpoint(resource.Resource):
def __init__(self, session):
resource.Resource.__init__(self)

child_handler_dict = {"statistics": TrustchainStatsEndpoint, "blocks": TrustchainBlocksEndpoint}
child_handler_dict = {
"statistics": TrustchainStatsEndpoint,
"blocks": TrustchainBlocksEndpoint,
"bootstrap": TrustchainBootstrapEndpoint
}

for path, child_cls in child_handler_dict.iteritems():
self.putChild(path, child_cls(session))
Expand Down Expand Up @@ -165,3 +169,67 @@ def render_GET(self, request):

blocks = mc_community.persistence.get_latest_blocks(self.identity.decode("HEX"), limit_blocks)
return json.dumps({"blocks": [dict(block) for block in blocks]})


class TrustchainBootstrapEndpoint(TrustchainBaseEndpoint):
"""
Bootstrap a new identity and transfer some bandwidth tokens to the new key.
"""

def render_GET(self, request):
"""
.. http:get:: /trustchain/bootstrap?amount=int
A GET request to this endpoint generates a new identity and transfers bandwidth tokens to it.
The amount specifies how much tokens need to be emptied into the new identity
**Example request**:
.. sourcecode:: none
curl -X GET http://localhost:8085/trustchain/bootstrap?amount=1000
**Example response**:
.. sourcecode:: javascript
{
"private_key": "TGliTmFDTFNLOmC4BR7otCpn+NzTBAFwKdSJdpT0KG9Zy5vPGX6s3rDXmNiDoGKyToLeYYB88vj9\nRj5NW
pbNf/ldcixYZ2YxQ7Q=\n",
"transaction": {
"down": 0,
"up": 1000
},
"block": {
"block_hash": "THJxNlKWMQG1Tio+Yz5CUCrnWahcyk6TDVfRLQf7w6M=\n",
"sequence_number": 1
}
}
"""

mc_community = self.get_trustchain_community()
if not mc_community:
request.setResponseCode(http.NOT_FOUND)
return json.dumps({"error": "trustchain community not found"})

available_tokens = mc_community.get_bandwidth_tokens()

if 'amount' in request.args:
try:
amount = int(request.args['amount'][0])
except ValueError:
request.setResponseCode(http.BAD_REQUEST)
return json.dumps({"error": "Provided token amount is not a number"})

if amount <= 0:
request.setResponseCode(http.BAD_REQUEST)
return json.dumps({"error": "Provided token amount is zero or negative"})
else:
amount = available_tokens

if amount <= 0 or amount > available_tokens:
request.setResponseCode(http.BAD_REQUEST)
return json.dumps({"error": "Not enough bandwidth tokens available"})

result = mc_community.bootstrap_new_identity(amount)
return json.dumps(result)
62 changes: 62 additions & 0 deletions Tribler/Test/Community/Triblerchain/test_community.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,65 @@ def test_get_default_trust(self):
other_trust = blockingCallFromThread(reactor, other.community.get_trust, node.community.my_member)
self.assertEqual(node_trust, 1)
self.assertEqual(other_trust, 1)

def test_get_bandwidth_tokens_for_self(self):
"""
Test that the bandwidth tokens the own node has is the upload - the download total of all blocks.
"""
# Arrange
node, other = self.create_nodes(2)
transaction = {'up': 10, 'down': 5}
transaction2 = {'up': 5, 'down': 10}
TestTriblerChainCommunity.create_block(node, other, self._create_target(node, other), transaction)
TestTriblerChainCommunity.create_block(other, node, self._create_target(other, node), transaction2)

# Get statistics
node_trust = blockingCallFromThread(reactor, node.community.get_bandwidth_tokens, node.community.my_member)
other_trust = blockingCallFromThread(reactor, other.community.get_bandwidth_tokens, other.community.my_member)
self.assertEqual(node_trust, 5)
self.assertEqual(other_trust, -5)

def test_get_bandwidth_tokens(self):
"""
Test that the bandwidth tokens nodes have is the upload - the download total of all blocks.
"""
# Arrange
node, other = self.create_nodes(2)
transaction = {'up': 10, 'down': 5}
transaction2 = {'up': 5, 'down': 10}
TestTriblerChainCommunity.create_block(node, other, self._create_target(node, other), transaction)
TestTriblerChainCommunity.create_block(other, node, self._create_target(other, node), transaction2)

# Get statistics
node_trust = blockingCallFromThread(reactor, node.community.get_bandwidth_tokens, other.community.my_member)
other_trust = blockingCallFromThread(reactor, other.community.get_bandwidth_tokens, node.community.my_member)
self.assertEqual(node_trust, -5)
self.assertEqual(other_trust, 5)

def test_get_default_bandwidth_tokens(self):
"""
Test that the bandwidth token amount for nodes without blocks is 0.
"""
# Arrange
node, other = self.create_nodes(2)

# Get statistics
node_trust = blockingCallFromThread(reactor, node.community.get_bandwidth_tokens, other.community.my_member)
other_trust = blockingCallFromThread(reactor, other.community.get_bandwidth_tokens, node.community.my_member)
self.assertEqual(node_trust, 0)
self.assertEqual(other_trust, 0)

def test_bootstrapping(self):
"""
Test that bootstrapping process works.
"""
# Arrange
node, = self.create_nodes(1)

node_bootstrap = blockingCallFromThread(reactor, node.community.bootstrap_new_identity, 100)
self.assertNotEqual(node_bootstrap['private_key'],
node.community.my_member.private_key.key_to_bin().encode('base64'))
self.assertEqual(node_bootstrap['transaction']['up'], 100)
self.assertEqual(node_bootstrap['transaction']['down'], 0)
self.assertEqual(node_bootstrap['block']['sequence_number'], 1)
self.assertNotEqual(node_bootstrap['block']['block_hash'], "")
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class TestBlock(TrustChainBlock):
Also used in other test files for TrustChain.
"""

def __init__(self, transaction=None, previous=None):
def __init__(self, transaction=None, previous=None, key=None):
crypto = ECCrypto()
other = crypto.generate_key(u"curve25519").pub().key_to_bin()

Expand All @@ -27,7 +27,11 @@ def __init__(self, transaction=None, previous=None):
TrustChainBlock.__init__(self, (encode(transaction), previous.public_key, previous.sequence_number + 1,
other, 0, previous.hash, 0, 0))
else:
self.key = crypto.generate_key(u"curve25519")
if key:
self.key = key
else:
self.key = crypto.generate_key(u"curve25519")

TrustChainBlock.__init__(self, (
encode(transaction), self.key.pub().key_to_bin(), random.randint(50, 100), other, 0,
sha256(str(random.randint(0, 100000))).digest(), 0, 0))
Expand Down
92 changes: 92 additions & 0 deletions Tribler/Test/Core/Modules/RestApi/test_trustchain_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,95 @@ def test_get_blocks_unlimited(self):
self.should_check_equality = False
return self.do_request('trustchain/blocks/%s' % TestBlock().public_key.encode("HEX"),
expected_code=200)

@deferred(timeout=10)
def test_get_bootstrap_identity_no_community(self):
"""
Testing whether the API returns error 404 if no trustchain community is loaded when bootstrapping a new identity
"""
self.dispersy.get_communities = lambda: []
return self.do_request('trustchain/bootstrap', expected_code=404)

@deferred(timeout=10)
def test_get_bootstrap_identity_all_tokens(self):
"""
Testing whether the API return all available credit when no argument is supplied
"""
transaction = {'up': 100, 'down': 0, 'total_up': 100, 'total_down': 0}
transaction2 = {'up': 100, 'down': 0}

def verify_response(response):
response_json = json.loads(response)
self.assertEqual(response_json['transaction'], transaction2)

test_block = TestBlock(transaction=transaction, key=self.tc_community.my_member.private_key)
self.tc_community.persistence.add_block(test_block)

self.should_check_equality = False
return self.do_request('trustchain/bootstrap', expected_code=200).addCallback(verify_response)

@deferred(timeout=10)
def test_get_bootstrap_identity_partial_tokens(self):
"""
Testing whether the API return partial available credit when argument is supplied
"""
transaction = {'up': 100, 'down': 0, 'total_up': 100, 'total_down': 0}
transaction2 = {'up': 50, 'down': 0}

def verify_response(response):
response_json = json.loads(response)
self.assertEqual(response_json['transaction'], transaction2)

test_block = TestBlock(transaction=transaction, key=self.tc_community.my_member.private_key)
self.tc_community.persistence.add_block(test_block)

self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=50', expected_code=200).addCallback(verify_response)

@deferred(timeout=10)
def test_get_bootstrap_identity_not_enough_tokens(self):
"""
Testing whether the API returns error 400 if bandwidth is to low when bootstrapping a new identity
"""
transaction = {'up': 100, 'down': 0, 'total_up': 100, 'total_down': 0}
test_block = TestBlock(transaction=transaction, key=self.tc_community.my_member.private_key)
self.tc_community.persistence.add_block(test_block)

self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=200', expected_code=400)

@deferred(timeout=10)
def test_get_bootstrap_identity_not_enough_tokens_2(self):
"""
Testing whether the API returns error 400 if bandwidth is to low when bootstrapping a new identity
"""
transaction = {'up': 0, 'down': 100, 'total_up': 0, 'total_down': 100}
test_block = TestBlock(transaction=transaction, key=self.tc_community.my_member.private_key)
self.tc_community.persistence.add_block(test_block)

self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=10', expected_code=400)

@deferred(timeout=10)
def test_get_bootstrap_identity_zero_amount(self):
"""
Testing whether the API returns error 400 if amount is zero when bootstrapping a new identity
"""
self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=0', expected_code=400)

@deferred(timeout=10)
def test_get_bootstrap_identity_negative_amount(self):
"""
Testing whether the API returns error 400 if amount is negative when bootstrapping a new identity
"""
self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=-1', expected_code=400)

@deferred(timeout=10)
def test_get_bootstrap_identity_string(self):
"""
Testing whether the API returns error 400 if amount is string when bootstrapping a new identity
"""
self.should_check_equality = False
return self.do_request('trustchain/bootstrap?amount=aaa', expected_code=400)
57 changes: 55 additions & 2 deletions Tribler/community/triblerchain/community.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from Tribler.community.trustchain.community import TrustChainCommunity
from Tribler.dispersy.util import blocking_call_on_reactor_thread

MIN_TRANSACTION_SIZE = 1024*1024
MIN_TRANSACTION_SIZE = 1024 * 1024


class PendingBytes(object):
Expand Down Expand Up @@ -163,7 +163,7 @@ def on_tunnel_remove(self, subject, change_type, tunnel, candidate):
from Tribler.community.tunnel.tunnel_community import Circuit, RelayRoute, TunnelExitSocket
assert isinstance(tunnel, Circuit) or isinstance(tunnel, RelayRoute) or isinstance(tunnel, TunnelExitSocket), \
"on_tunnel_remove() was called with an object that is not a Circuit, RelayRoute or TunnelExitSocket"
assert isinstance(tunnel.bytes_up, int) and isinstance(tunnel.bytes_down, int),\
assert isinstance(tunnel.bytes_up, int) and isinstance(tunnel.bytes_down, int), \
"tunnel instance must provide byte counts in int"

up = tunnel.bytes_up
Expand Down Expand Up @@ -215,6 +215,59 @@ def get_trust(self, member):
# We need a minimum of 1 trust to have a chance to be selected in the categorical distribution.
return 1

def get_bandwidth_tokens(self, member=None):
"""
Get the bandwidth tokens for another member.
Currently this is just the difference in the amount of MBs exchanged with them.
:param member: the member we interacted with
:type member: dispersy.member.Member
:return: the amount of bandwidth tokens for this member
:rtype: int
"""
if member is None:
member = self.my_member

block = self.persistence.get_latest(member.public_key)
if block:
return block.transaction['total_up'] - block.transaction['total_down']

return 0

def bootstrap_new_identity(self, amount):
"""
One-way payment channel.
Create a new temporary identity, and transfer funds to the new identity.
A different party can then take the result and do a transfer from the temporary identity to itself
"""

# Create new identity for the temporary identity
tmp_member = self.dispersy.get_new_member(u"curve25519")

# Create the transaction specification
transaction = {
'up': 0, 'down': amount
}

# Create the two half blocks that form the transaction
local_half_block = TriblerChainBlock.create(transaction, self.persistence, self.my_member.public_key,
link_pk=tmp_member.public_key)
local_half_block.sign(self.my_member.private_key)
tmp_half_block = TriblerChainBlock.create(transaction, self.persistence, tmp_member.public_key,
link=local_half_block, link_pk=self.my_member.public_key)
tmp_half_block.sign(tmp_member.private_key)

self.persistence.add_block(local_half_block)
self.persistence.add_block(tmp_half_block)

# Create the bootstrapped identity format
block = {'block_hash': tmp_half_block.hash.encode('base64'),
'sequence_number': tmp_half_block.sequence_number}

result = {'private_key': tmp_member.private_key.key_to_bin().encode('base64'),
'transaction': {'up': amount, 'down': 0}, 'block': block}
return result


class TriblerChainCommunityCrawler(TriblerChainCommunity):
"""
Expand Down
Loading

0 comments on commit 0d9041a

Please sign in to comment.