Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-way communication offline funds transfer using QR-code #3358

Merged
merged 23 commits into from
Jan 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a385933
Added ability to export chain keys
mitchellolsthoorn Dec 2, 2017
12d7523
Made the loading of the qrcode module and the keys fault tolerant
mitchellolsthoorn Dec 2, 2017
28965a1
Changes base64 QR Code encoding
PauluzzNL Dec 14, 2017
c18c8dd
headless startup
Dec 15, 2017
0ace718
fake endpoint
Dec 15, 2017
c1190ec
read out up and down paramters + fix documentation
Dec 15, 2017
3aedb31
use realish blocks
Dec 15, 2017
27c652c
Updated the QR code generation with the new endpoint
mitchellolsthoorn Dec 19, 2017
4827ddb
Add todo Use base64 to reduce space ( block key's are still in hex , …
Dec 19, 2017
2cac41c
Drop old QR buttons
Jan 8, 2018
0e8b7d4
Decrease JSON size by proper base64 encoding
Jan 8, 2018
18e3233
remove hash from block
Jan 8, 2018
e0d2c8e
remove insert time
Jan 8, 2018
f1d797a
fix signing logic + add rather large doc
Jan 9, 2018
19f15c2
Updated the code with some improvements
mitchellolsthoorn Jan 10, 2018
c12a25c
Removed data sent in the QR code
mitchellolsthoorn Jan 11, 2018
4745d4a
Removed the array syntax in the QR code
mitchellolsthoorn Jan 11, 2018
887394e
Removed PDF417 support
mitchellolsthoorn Jan 14, 2018
278bd3a
Removed redundant constants
mitchellolsthoorn Jan 14, 2018
f9bc04d
Changed button and label text in the GUI
mitchellolsthoorn Jan 16, 2018
e48a7b6
Removed total_up and total_down from transaction
mitchellolsthoorn Jan 17, 2018
41bc3d5
Added partial funds support and extra conformation screen and changed…
mitchellolsthoorn Jan 17, 2018
08f92e3
Added tests, fixed naming and fixed max amount
mitchellolsthoorn Jan 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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