mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Add client code to create/transfer txs
This commit is contained in:
parent
054c17d38c
commit
70692a851c
@ -39,7 +39,8 @@ config = {
|
|||||||
'host': e('BIGCHAIN_STATSD_HOST', default='localhost'),
|
'host': e('BIGCHAIN_STATSD_HOST', default='localhost'),
|
||||||
'port': e('BIGCHAIN_STATSD_PORT', default=8125),
|
'port': e('BIGCHAIN_STATSD_PORT', default=8125),
|
||||||
'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01)
|
'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01)
|
||||||
}
|
},
|
||||||
|
'api_endpoint': 'http://localhost:8008/api/v1'
|
||||||
}
|
}
|
||||||
|
|
||||||
# We need to maintain a backup copy of the original config dict in case
|
# We need to maintain a backup copy of the original config dict in case
|
||||||
|
113
bigchaindb/client.py
Normal file
113
bigchaindb/client.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
import bigchaindb
|
||||||
|
from bigchaindb import util
|
||||||
|
from bigchaindb import config_utils
|
||||||
|
from bigchaindb import exceptions
|
||||||
|
from bigchaindb import crypto
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
"""Client for BigchainDB.
|
||||||
|
|
||||||
|
A Client is initialized with a keypair and is able to create, sign, and submit transactions to a Node
|
||||||
|
in the Federation. At the moment, a Client instance is bounded to a specific ``host`` in the Federation.
|
||||||
|
In the future, a Client might connect to >1 hosts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, public_key=None, private_key=None, api_endpoint=None):
|
||||||
|
"""Initialize the Client instance
|
||||||
|
|
||||||
|
There are three ways in which the Client instance can get its parameters.
|
||||||
|
The order by which the parameters are chosen are:
|
||||||
|
|
||||||
|
1. Setting them by passing them to the `__init__` method itself.
|
||||||
|
2. Setting them as environment variables
|
||||||
|
3. Reading them from the `config.json` file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
|
||||||
|
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
|
||||||
|
host (str): hostname where the rethinkdb is running.
|
||||||
|
port (int): port in which rethinkb is running (usually 28015).
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_utils.autoconfigure()
|
||||||
|
|
||||||
|
self.public_key = public_key or bigchaindb.config['keypair']['public']
|
||||||
|
self.private_key = private_key or bigchaindb.config['keypair']['private']
|
||||||
|
self.api_endpoint = api_endpoint or bigchaindb.config['api_endpoint']
|
||||||
|
|
||||||
|
if not self.public_key or not self.private_key:
|
||||||
|
raise exceptions.KeypairNotFoundException()
|
||||||
|
|
||||||
|
def make_tx(self, new_owner, tx_input, operation='TRANSFER', payload=None):
|
||||||
|
"""Make a new transaction
|
||||||
|
|
||||||
|
Refer to the documentation of ``bigchaindb.util.create_tx``
|
||||||
|
"""
|
||||||
|
|
||||||
|
return util.create_tx(self.public_key, new_owner, tx_input, operation, payload)
|
||||||
|
|
||||||
|
def sign_tx(self, tx):
|
||||||
|
"""Sign a transaction
|
||||||
|
|
||||||
|
Refer to the documentation of ``bigchaindb.util.sign_tx``
|
||||||
|
"""
|
||||||
|
|
||||||
|
return util.sign_tx(tx, self.private_key)
|
||||||
|
|
||||||
|
def push_tx(self, tx):
|
||||||
|
"""Submit a transaction to the Federation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tx (dict): the transaction to be pushed to the Federation.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The transaction pushed to the Federation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
res = requests.post(self.api_endpoint + '/tx/', json=tx)
|
||||||
|
return res.json()
|
||||||
|
|
||||||
|
def create(self, payload=None):
|
||||||
|
"""Create a transaction.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payload (dict): the payload for the transaction.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The transaction pushed to the Federation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tx = self.make_tx(self.public_key, None, operation='CREATE', payload=payload)
|
||||||
|
signed_tx = self.sign_tx(tx)
|
||||||
|
return self.push_tx(signed_tx)
|
||||||
|
|
||||||
|
def transfer(self, new_owner, tx_input, payload=None):
|
||||||
|
"""Transfer a transaction.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_owner (str): the public key of the new owner
|
||||||
|
tx_input (str): the id of the transaction to use as input
|
||||||
|
payload (dict, optional): the payload for the transaction.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The transaction pushed to the Federation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tx = self.make_tx(new_owner, tx_input, payload=payload)
|
||||||
|
signed_tx = self.sign_tx(tx)
|
||||||
|
return self.push_tx(signed_tx)
|
||||||
|
|
||||||
|
|
||||||
|
def temp_client():
|
||||||
|
"""Create a new temporary client.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A client initialized with a keypair generated on the fly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
private_key, public_key = crypto.generate_key_pair()
|
||||||
|
return Client(private_key=private_key, public_key=public_key, api_endpoint='http://localhost:5000')
|
||||||
|
|
@ -43,8 +43,8 @@ class Bigchain(object):
|
|||||||
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
|
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
|
||||||
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
|
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
|
||||||
keyring (list[str]): list of base58 encoded public keys of the federation nodes.
|
keyring (list[str]): list of base58 encoded public keys of the federation nodes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_utils.autoconfigure()
|
config_utils.autoconfigure()
|
||||||
self.host = host or bigchaindb.config['database']['host']
|
self.host = host or bigchaindb.config['database']['host']
|
||||||
self.port = port or bigchaindb.config['database']['port']
|
self.port = port or bigchaindb.config['database']['port']
|
||||||
|
@ -6,6 +6,7 @@ import rethinkdb as r
|
|||||||
from bigchaindb import Bigchain
|
from bigchaindb import Bigchain
|
||||||
from bigchaindb.voter import Voter
|
from bigchaindb.voter import Voter
|
||||||
from bigchaindb.block import Block
|
from bigchaindb.block import Block
|
||||||
|
from bigchaindb.web import server
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -80,3 +81,9 @@ class Processes(object):
|
|||||||
|
|
||||||
logger.info('starting voter')
|
logger.info('starting voter')
|
||||||
p_voter.start()
|
p_voter.start()
|
||||||
|
|
||||||
|
# start the web api
|
||||||
|
webapi = server.create_app()
|
||||||
|
p_webapi = mp.Process(name='webapi', target=webapi.run, kwargs={'host': '0.0.0.0'})
|
||||||
|
p_webapi.start()
|
||||||
|
|
||||||
|
1
setup.py
1
setup.py
@ -74,6 +74,7 @@ setup(
|
|||||||
'base58==0.2.2',
|
'base58==0.2.2',
|
||||||
'bitcoin==1.1.42',
|
'bitcoin==1.1.42',
|
||||||
'flask==0.10.1',
|
'flask==0.10.1',
|
||||||
|
'requests==2.9',
|
||||||
],
|
],
|
||||||
setup_requires=['pytest-runner'],
|
setup_requires=['pytest-runner'],
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
|
94
tests/test_client.py
Normal file
94
tests/test_client.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
from bigchaindb.client import temp_client
|
||||||
|
return temp_client()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_requests_post(monkeypatch):
|
||||||
|
class MockResponse:
|
||||||
|
def __init__(self, json):
|
||||||
|
self._json = json
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self._json
|
||||||
|
|
||||||
|
def mockreturn(*args, **kwargs):
|
||||||
|
return MockResponse(kwargs.get('json'))
|
||||||
|
|
||||||
|
monkeypatch.setattr('requests.post', mockreturn)
|
||||||
|
|
||||||
|
|
||||||
|
def test_temp_client_returns_a_temp_client():
|
||||||
|
from bigchaindb.client import temp_client
|
||||||
|
client = temp_client()
|
||||||
|
assert client.public_key
|
||||||
|
assert client.private_key
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_can_make_transactions(client):
|
||||||
|
tx = client.make_tx('a', 123)
|
||||||
|
|
||||||
|
assert tx['transaction']['current_owner'] == client.public_key
|
||||||
|
assert tx['transaction']['new_owner'] == 'a'
|
||||||
|
assert tx['transaction']['input'] == 123
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_can_sign_transactions(client):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
tx = client.make_tx('a', 123)
|
||||||
|
signed_tx = client.sign_tx(tx)
|
||||||
|
|
||||||
|
assert signed_tx['transaction']['current_owner'] == client.public_key
|
||||||
|
assert signed_tx['transaction']['new_owner'] == 'a'
|
||||||
|
assert signed_tx['transaction']['input'] == 123
|
||||||
|
|
||||||
|
assert util.verify_signature(signed_tx)
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_can_push_transactions(mock_requests_post, client):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
tx = client.make_tx('a', 123)
|
||||||
|
signed_tx = client.sign_tx(tx)
|
||||||
|
ret_tx = client.push_tx(signed_tx)
|
||||||
|
|
||||||
|
assert ret_tx['transaction']['current_owner'] == client.public_key
|
||||||
|
assert ret_tx['transaction']['new_owner'] == 'a'
|
||||||
|
assert ret_tx['transaction']['input'] == 123
|
||||||
|
|
||||||
|
assert util.verify_signature(ret_tx)
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_can_create_transactions_using_shortcut_method(mock_requests_post, client):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
tx = client.create()
|
||||||
|
|
||||||
|
# XXX: `CREATE` operations require the node that receives the transaction to modify the data in
|
||||||
|
# the transaction itself.
|
||||||
|
# `current_owner` will be overwritten with the public key of the node in the federation
|
||||||
|
# that will create the real transaction. `signature` will be overwritten with the new signature.
|
||||||
|
# Note that this scenario is ignored by this test.
|
||||||
|
assert tx['transaction']['current_owner'] == client.public_key
|
||||||
|
assert tx['transaction']['new_owner'] == client.public_key
|
||||||
|
assert tx['transaction']['input'] == None
|
||||||
|
|
||||||
|
assert util.verify_signature(tx)
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_can_transfer_transactions_using_shortcut_method(mock_requests_post, client):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
tx = client.transfer('a', 123)
|
||||||
|
|
||||||
|
assert tx['transaction']['current_owner'] == client.public_key
|
||||||
|
assert tx['transaction']['new_owner'] == 'a'
|
||||||
|
assert tx['transaction']['input'] == 123
|
||||||
|
|
||||||
|
assert util.verify_signature(tx)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user