Enhance integration test suite (#62)

* restructering, added helper, split cli tests for later

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed threshold test

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added acceptance tests to integration test suite

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added different threshold signature test scenarios

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* started chain-migration test implementation

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed linter errors

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed -s from test command

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
Lorenz Herzberger 2022-03-24 14:24:32 +01:00 committed by GitHub
parent f9f1a64773
commit df7c1e1ccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1151 additions and 71 deletions

View File

@ -46,6 +46,7 @@ services:
volumes:
- ./integration/python/src:/src
- ./integration/scripts:/scripts
- ./integration/cli:/tests
- shared:/shared
volumes:

View File

@ -83,18 +83,6 @@ services:
environment:
- PLANETMINT_ENDPOINT=planetmint
# Planetmint setup to do integration testing wtih Python
python-integration:
build:
context: .
dockerfile: ./integration/python/Dockerfile
volumes:
- ./integration/python/docs:/docs
- ./integration/python/src:/src
environment:
- PLANETMINT_ENDPOINT_1=https://itest1.planetmint.io
- PLANETMINT_ENDPOINT_2=https://itest2.planetmint.io
# Build docs only
# docker-compose build bdocs
# docker-compose up -d bdocs

View File

@ -0,0 +1,47 @@
#!/bin/bash
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# Add chain migration test
check_status () {
status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@$1 'bash -s' < scripts/election.sh show_election $2 | tail -n 1)
status=${status#*=}
if [ $status != $3 ]; then
exit 1
fi
}
# Read host names from shared
readarray -t HOSTNAMES < /shared/hostnames
# Split into proposer and approvers
PROPOSER=${HOSTNAMES[0]}
APPROVERS=${HOSTNAMES[@]:1}
# Propose chain migration
result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh migrate)
# Check if election is ongoing and approve chain migration
for APPROVER in ${APPROVERS[@]}; do
# Check if election is still ongoing
check_status ${APPROVER} $result ongoing
ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${APPROVER} 'bash -s' < scripts/election.sh approve $result
done
# Status of election should be concluded
status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh show_election $result)
status=${status#*INFO:planetmint.commands.planetmint:}
status=("$status[@]")
# TODO: Get status, chain_id, app_hash and validators to restore planetmint on all nodes
# References:
# https://github.com/bigchaindb/BEPs/tree/master/42
# http://docs.bigchaindb.com/en/latest/installation/node-setup/bigchaindb-cli.html
for word in $status; do
echo $word
done
echo ${status#*validators=}

View File

@ -0,0 +1,33 @@
#!/bin/bash
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
check_status () {
status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@$1 'bash -s' < scripts/election.sh show_election $2 | tail -n 1)
status=${status#*=}
if [ $status != $3 ]; then
exit 1
fi
}
# Read host names from shared
readarray -t HOSTNAMES < /shared/hostnames
# Split into proposer and approvers
PROPOSER=${HOSTNAMES[0]}
APPROVERS=${HOSTNAMES[@]:1}
# Propose validator upsert
result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh elect 2)
# Check if election is ongoing and approve validator upsert
for APPROVER in ${APPROVERS[@]}; do
# Check if election is still ongoing
check_status ${APPROVER} $result ongoing
ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${APPROVER} 'bash -s' < scripts/election.sh approve $result
done
# Status of election should be concluded
check_status ${PROPOSER} $result concluded

View File

@ -1,9 +1,17 @@
FROM python:3.9
RUN apt-get update \
&& pip install -U pip \
&& apt-get autoremove \
&& apt-get clean
RUN apt-get install -y vim zsh build-essential cmake openssh-client openssh-server
RUN mkdir -p /src
RUN pip install --upgrade \
pytest~=6.2.5 \
planetmint-driver~=0.9.0 \
pycco
RUN apt-get update && apt-get install -y openssh-client openssh-server
pycco \
websocket-client~=0.47.0 \
git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \
git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \
blns

View File

View File

@ -0,0 +1,95 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
import pytest
GENERATE_KEYPAIR = \
"""Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': Create the keypair
Given that I am known as 'Pippo'
When I create the ecdh key
When I create the testnet key
Then print data"""
# secret key to public key
SK_TO_PK = \
"""Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keys'
When I create the ecdh public key
When I create the testnet address
Then print my 'ecdh public key'
Then print my 'testnet address'"""
FULFILL_SCRIPT = \
"""Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': Bob verifies the signature from Alice
Given I have a 'ecdh public key' from 'Alice'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given I have a 'signature' named 'data.signature' inside 'result'
When I verify the 'houses' has a signature in 'data.signature' by 'Alice'
Then print the string 'ok'"""
HOUSE_ASSETS = {
"data": {
"houses": [
{
"name": "Harry",
"team": "Gryffindor",
},
{
"name": "Draco",
"team": "Slytherin",
}
],
}
}
ZENROOM_DATA = {
'also': 'more data'
}
CONDITION_SCRIPT = """Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': create the signature of an object
Given I have the 'keys'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
When I create the signature of 'houses'
When I rename the 'signature' to 'data.signature'
Then print the 'data.signature'"""
@pytest.fixture
def gen_key_zencode():
return GENERATE_KEYPAIR
@pytest.fixture
def secret_key_to_private_key_zencode():
return SK_TO_PK
@pytest.fixture
def fulfill_script_zencode():
return FULFILL_SCRIPT
@pytest.fixture
def condition_script_zencode():
return CONDITION_SCRIPT
@pytest.fixture
def zenroom_house_assets():
return HOUSE_ASSETS
@pytest.fixture
def zenroom_data():
return ZENROOM_DATA

View File

@ -0,0 +1,36 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
from typing import List
from planetmint_driver import Planetmint
class Hosts:
hostnames = []
connections = []
def __init__(self, filepath):
self.set_hostnames(filepath=filepath)
self.set_connections()
def set_hostnames(self, filepath) -> None:
with open(filepath) as f:
self.hostnames = f.readlines()
def set_connections(self) -> None:
self.connections = list(map(lambda h: Planetmint(h), self.hostnames))
def get_connection(self, index=0) -> Planetmint:
return self.connections[index]
def get_transactions(self, tx_id) -> List:
return list(map(lambda connection: connection.transactions.retrieve(tx_id), self.connections))
def assert_transaction(self, tx_id) -> None:
txs = self.get_transactions(tx_id)
for tx in txs:
assert txs[0] == tx, \
'Cannot find transaction {}'.format(tx_id)

View File

@ -4,21 +4,18 @@
# Code is Apache-2.0 and docs are CC-BY-4.0
# import Planetmint and create object
from planetmint_driver import Planetmint
from planetmint_driver.crypto import generate_keypair
# import helper to manage multiple nodes
from .helper.hosts import Hosts
import time
def test_basic():
# Setup up connection to Planetmint integration test nodes
hosts = []
with open('/shared/hostnames') as f:
hosts = f.readlines()
pm_hosts = list(map(lambda x: Planetmint(x), hosts))
pm_alpha = pm_hosts[0]
pm_betas = pm_hosts[1:]
hosts = Hosts('/shared/hostnames')
pm_alpha = hosts.get_connection()
# genarate a keypair
alice = generate_keypair()
@ -50,13 +47,8 @@ def test_basic():
creation_tx_id = fulfilled_creation_tx['id']
# retrieve transactions from all planetmint nodes
creation_tx_alpha = pm_alpha.transactions.retrieve(creation_tx_id)
creation_tx_betas = list(map(lambda beta: beta.transactions.retrieve(creation_tx_id), pm_betas))
# Assert that transaction is stored on all planetmint nodes
for tx in creation_tx_betas:
assert creation_tx_alpha == tx
hosts.assert_transaction(creation_tx_id)
# Transfer
# create the output and inout for the transaction
@ -87,10 +79,5 @@ def test_basic():
transfer_tx_id = sent_transfer_tx['id']
# retrieve transactions from both planetmint nodes
transfer_tx_alpha = pm_alpha.transactions.retrieve(transfer_tx_id)
transfer_tx_betas = list(map(lambda beta: beta.transactions.retrieve(transfer_tx_id), pm_betas))
# Assert that transaction is stored on both planetmint nodes
for tx in transfer_tx_betas:
assert transfer_tx_alpha == tx
hosts.assert_transaction(transfer_tx_id)

View File

@ -0,0 +1,183 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# # Divisible assets integration testing
# This test checks if we can successfully divide assets.
# The script tests various things like:
#
# - create a transaction with a divisible asset and issue them to someone
# - check if the transaction is stored and has the right amount of tokens
# - spend some tokens
# - try to spend more tokens than available
#
# We run a series of checks for each step, that is retrieving
# the transaction from the remote system, and also checking the `amount`
# of a given transaction.
#
# This integration test is a rip-off of our
# [tutorial](https://docs.planetmint.com/projects/py-driver/en/latest/usage.html).
# ## Imports
# We need the `pytest` package to catch the `BadRequest` exception properly.
# And of course, we also need the `BadRequest`.
import pytest
from planetmint_driver.exceptions import BadRequest
# Import generate_keypair to create actors
from planetmint_driver.crypto import generate_keypair
# import helper to manage multiple nodes
from .helper.hosts import Hosts
def test_divisible_assets():
# ## Set up a connection to Planetmint
# Check [test_basic.py](./test_basic.html) to get some more details
# about the endpoint.
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
# Oh look, it is Alice again and she brought her friend Bob along.
alice, bob = generate_keypair(), generate_keypair()
# ## Alice creates a time sharing token
# Alice wants to go on vacation, while Bobs bike just broke down.
# Alice decides to rent her bike to Bob while she is gone.
# So she prepares a `CREATE` transaction to issues 10 tokens.
# First, she prepares an asset for a time sharing token. As you can see in
# the description, Bob and Alice agree that each token can be used to ride
# the bike for one hour.
bike_token = {
'data': {
'token_for': {
'bike': {
'serial_number': 420420
}
},
'description': 'Time share token. Each token equals one hour of riding.',
},
}
# She prepares a `CREATE` transaction and issues 10 tokens.
# Here, Alice defines in a tuple that she wants to assign
# these 10 tokens to Bob.
prepared_token_tx = pm.transactions.prepare(
operation='CREATE',
signers=alice.public_key,
recipients=[([bob.public_key], 10)],
asset=bike_token)
# She fulfills and sends the transaction.
fulfilled_token_tx = pm.transactions.fulfill(
prepared_token_tx,
private_keys=alice.private_key)
pm.transactions.send_commit(fulfilled_token_tx)
# We store the `id` of the transaction to use it later on.
bike_token_id = fulfilled_token_tx['id']
# Let's check if the transaction was successful.
assert pm.transactions.retrieve(bike_token_id), \
'Cannot find transaction {}'.format(bike_token_id)
# Bob owns 10 tokens now.
assert pm.transactions.retrieve(bike_token_id)['outputs'][0][
'amount'] == '10'
# ## Bob wants to use the bike
# Now that Bob got the tokens and the sun is shining, he wants to get out
# with the bike for three hours.
# To use the bike he has to send the tokens back to Alice.
# To learn about the details of transferring a transaction check out
# [test_basic.py](./test_basic.html)
transfer_asset = {'id': bike_token_id}
output_index = 0
output = fulfilled_token_tx['outputs'][output_index]
transfer_input = {'fulfillment': output['condition']['details'],
'fulfills': {'output_index': output_index,
'transaction_id': fulfilled_token_tx[
'id']},
'owners_before': output['public_keys']}
# To use the tokens Bob has to reassign 7 tokens to himself and the
# amount he wants to use to Alice.
prepared_transfer_tx = pm.transactions.prepare(
operation='TRANSFER',
asset=transfer_asset,
inputs=transfer_input,
recipients=[([alice.public_key], 3), ([bob.public_key], 7)])
# He signs and sends the transaction.
fulfilled_transfer_tx = pm.transactions.fulfill(
prepared_transfer_tx,
private_keys=bob.private_key)
sent_transfer_tx = pm.transactions.send_commit(fulfilled_transfer_tx)
# First, Bob checks if the transaction was successful.
assert pm.transactions.retrieve(
fulfilled_transfer_tx['id']) == sent_transfer_tx
hosts.assert_transaction(fulfilled_transfer_tx['id'])
# There are two outputs in the transaction now.
# The first output shows that Alice got back 3 tokens...
assert pm.transactions.retrieve(
fulfilled_transfer_tx['id'])['outputs'][0]['amount'] == '3'
# ... while Bob still has 7 left.
assert pm.transactions.retrieve(
fulfilled_transfer_tx['id'])['outputs'][1]['amount'] == '7'
# ## Bob wants to ride the bike again
# It's been a week and Bob wants to right the bike again.
# Now he wants to ride for 8 hours, that's a lot Bob!
# He prepares the transaction again.
transfer_asset = {'id': bike_token_id}
# This time we need an `output_index` of 1, since we have two outputs
# in the `fulfilled_transfer_tx` we created before. The first output with
# index 0 is for Alice and the second output is for Bob.
# Since Bob wants to spend more of his tokens he has to provide the
# correct output with the correct amount of tokens.
output_index = 1
output = fulfilled_transfer_tx['outputs'][output_index]
transfer_input = {'fulfillment': output['condition']['details'],
'fulfills': {'output_index': output_index,
'transaction_id': fulfilled_transfer_tx['id']},
'owners_before': output['public_keys']}
# This time Bob only provides Alice in the `recipients` because he wants
# to spend all his tokens
prepared_transfer_tx = pm.transactions.prepare(
operation='TRANSFER',
asset=transfer_asset,
inputs=transfer_input,
recipients=[([alice.public_key], 8)])
fulfilled_transfer_tx = pm.transactions.fulfill(
prepared_transfer_tx,
private_keys=bob.private_key)
# Oh Bob, what have you done?! You tried to spend more tokens than you had.
# Remember Bob, last time you spent 3 tokens already,
# so you only have 7 left.
with pytest.raises(BadRequest) as error:
pm.transactions.send_commit(fulfilled_transfer_tx)
# Now Bob gets an error saying that the amount he wanted to spent is
# higher than the amount of tokens he has left.
assert error.value.args[0] == 400
message = 'Invalid transaction (AmountError): The amount used in the ' \
'inputs `7` needs to be same as the amount used in the ' \
'outputs `8`'
assert error.value.args[2]['message'] == message
# We have to stop this test now, I am sorry, but Bob is pretty upset
# about his mistake. See you next time :)

View File

@ -0,0 +1,48 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# # Double Spend testing
# This test challenge the system with double spends.
from uuid import uuid4
from threading import Thread
import queue
import planetmint_driver.exceptions
from planetmint_driver.crypto import generate_keypair
from .helper.hosts import Hosts
def test_double_create():
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
alice = generate_keypair()
results = queue.Queue()
tx = pm.transactions.fulfill(
pm.transactions.prepare(
operation='CREATE',
signers=alice.public_key,
asset={'data': {'uuid': str(uuid4())}}),
private_keys=alice.private_key)
def send_and_queue(tx):
try:
pm.transactions.send_commit(tx)
results.put('OK')
except planetmint_driver.exceptions.TransportError:
results.put('FAIL')
t1 = Thread(target=send_and_queue, args=(tx, ))
t2 = Thread(target=send_and_queue, args=(tx, ))
t1.start()
t2.start()
results = [results.get(timeout=2), results.get(timeout=2)]
assert results.count('OK') == 1
assert results.count('FAIL') == 1

View File

@ -22,20 +22,16 @@
import time
# For this test case we need import and use the Python driver
from planetmint_driver import Planetmint
from planetmint_driver.crypto import generate_keypair
# Import helper to deal with multiple nodes
from .helper.hosts import Hosts
def test_multiple_owners():
# Setup up connection to Planetmint integration test nodes
hosts = []
with open('/shared/hostnames') as f:
hosts = f.readlines()
pm_hosts = list(map(lambda x: Planetmint(x), hosts))
pm_alpha = pm_hosts[0]
pm_betas = pm_hosts[1:]
hosts = Hosts('/shared/hostnames')
pm_alpha = hosts.get_connection()
# Generate Keypairs for Alice and Bob!
alice, bob = generate_keypair(), generate_keypair()
@ -73,13 +69,9 @@ def test_multiple_owners():
dw_id = fulfilled_dw_tx['id']
time.sleep(1)
# Let's retrieve the transaction from both nodes
pm_alpha_tx = pm_alpha.transactions.retrieve(dw_id)
pm_betas_tx = list(map(lambda beta: beta.transactions.retrieve(dw_id), pm_betas))
# Both retrieved transactions should be the same
for tx in pm_betas_tx:
assert pm_alpha_tx == tx
# Use hosts to assert that the transaction is properly propagated to every node
hosts.assert_transaction(dw_id)
# Let's check if the transaction was successful.
assert pm_alpha.transactions.retrieve(dw_id), \
@ -124,13 +116,8 @@ def test_multiple_owners():
sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx)
time.sleep(1)
# Retrieve the fulfilled transaction from both nodes
pm_alpha_tx = pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])
pm_betas_tx = list(map(lambda beta: beta.transactions.retrieve(fulfilled_transfer_tx['id']), pm_betas))
# Now compare if both nodes returned the same transaction
for tx in pm_betas_tx:
assert pm_alpha_tx == tx
hosts.assert_transaction(fulfilled_transfer_tx['id'])
# They check if the transaction was successful.
assert pm_alpha.transactions.retrieve(

View File

@ -0,0 +1,100 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# ## Testing potentially hazardous strings
# This test uses a library of `naughty` strings (code injections, weird unicode chars., etc.) as both keys and values.
# We look for either a successful tx, or in the case that we use a naughty string as a key, and it violates some key
# constraints, we expect to receive a well formatted error message.
# ## Imports
# Since the naughty strings get encoded and decoded in odd ways,
# we'll use a regex to sweep those details under the rug.
import re
# We'll use a nice library of naughty strings...
from blns import blns
# And parameterize our test so each one is treated as a separate test case
import pytest
# For this test case we import and use the Python Driver.
from planetmint_driver.crypto import generate_keypair
from planetmint_driver.exceptions import BadRequest
# import helper to manage multiple nodes
from .helper.hosts import Hosts
naughty_strings = blns.all()
# This is our base test case, but we'll reuse it to send naughty strings as both keys and values.
def send_naughty_tx(asset, metadata):
# ## Set up a connection to Planetmint
# Check [test_basic.py](./test_basic.html) to get some more details
# about the endpoint.
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
# Here's Alice.
alice = generate_keypair()
# Alice is in a naughty mood today, so she creates a tx with some naughty strings
prepared_transaction = pm.transactions.prepare(
operation='CREATE',
signers=alice.public_key,
asset=asset,
metadata=metadata)
# She fulfills the transaction
fulfilled_transaction = pm.transactions.fulfill(
prepared_transaction,
private_keys=alice.private_key)
# The fulfilled tx gets sent to the pm network
try:
sent_transaction = pm.transactions.send_commit(fulfilled_transaction)
except BadRequest as e:
sent_transaction = e
# If her key contained a '.', began with a '$', or contained a NUL character
regex = r'.*\..*|\$.*|.*\x00.*'
key = next(iter(metadata))
if re.match(regex, key):
# Then she expects a nicely formatted error code
status_code = sent_transaction.status_code
error = sent_transaction.error
regex = (
r'\{\s*\n*'
r'\s*"message":\s*"Invalid transaction \(ValidationError\):\s*'
r'Invalid key name.*The key name cannot contain characters.*\n*'
r'\s*"status":\s*400\n*'
r'\s*\}\n*')
assert status_code == 400
assert re.fullmatch(regex, error), sent_transaction
# Otherwise, she expects to see her transaction in the database
elif 'id' in sent_transaction.keys():
tx_id = sent_transaction['id']
assert pm.transactions.retrieve(tx_id)
# If neither condition was true, then something weird happened...
else:
raise TypeError(sent_transaction)
@pytest.mark.parametrize("naughty_string", naughty_strings, ids=naughty_strings)
def test_naughty_keys(naughty_string):
asset = {'data': {naughty_string: 'nice_value'}}
metadata = {naughty_string: 'nice_value'}
send_naughty_tx(asset, metadata)
@pytest.mark.parametrize("naughty_string", naughty_strings, ids=naughty_strings)
def test_naughty_values(naughty_string):
asset = {'data': {'nice_key': naughty_string}}
metadata = {'nice_key': naughty_string}
send_naughty_tx(asset, metadata)

View File

@ -0,0 +1,131 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# # Stream Acceptance Test
# This test checks if the event stream works correctly. The basic idea of this
# test is to generate some random **valid** transaction, send them to a
# Planetmint node, and expect those transactions to be returned by the valid
# transactions Stream API. During this test, two threads work together,
# sharing a queue to exchange events.
#
# - The *main thread* first creates and sends the transactions to Planetmint;
# then it run through all events in the shared queue to check if all
# transactions sent have been validated by Planetmint.
# - The *listen thread* listens to the events coming from Planetmint and puts
# them in a queue shared with the main thread.
import queue
import json
from threading import Thread, Event
from uuid import uuid4
# For this script, we need to set up a websocket connection, that's the reason
# we import the
# [websocket](https://github.com/websocket-client/websocket-client) module
from websocket import create_connection
from planetmint_driver.crypto import generate_keypair
# import helper to manage multiple nodes
from .helper.hosts import Hosts
def test_stream():
# ## Set up the test
# We use the env variable `BICHAINDB_ENDPOINT` to know where to connect.
# Check [test_basic.py](./test_basic.html) for more information.
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
# *That's pretty bad, but let's do like this for now.*
WS_ENDPOINT = 'ws://{}:9985/api/v1/streams/valid_transactions'.format(hosts.hostnames[0])
# Hello to Alice again, she is pretty active in those tests, good job
# Alice!
alice = generate_keypair()
# We need few variables to keep the state, specifically we need `sent` to
# keep track of all transactions Alice sent to Planetmint, while `received`
# are the transactions Planetmint validated and sent back to her.
sent = []
received = queue.Queue()
# In this test we use a websocket. The websocket must be started **before**
# sending transactions to Planetmint, otherwise we might lose some
# transactions. The `ws_ready` event is used to synchronize the main thread
# with the listen thread.
ws_ready = Event()
# ## Listening to events
# This is the function run by the complementary thread.
def listen():
# First we connect to the remote endpoint using the WebSocket protocol.
ws = create_connection(WS_ENDPOINT)
# After the connection has been set up, we can signal the main thread
# to proceed (continue reading, it should make sense in a second.)
ws_ready.set()
# It's time to consume all events coming from the Planetmint stream API.
# Every time a new event is received, it is put in the queue shared
# with the main thread.
while True:
result = ws.recv()
received.put(result)
# Put `listen` in a thread, and start it. Note that `listen` is a local
# function and it can access all variables in the enclosing function.
t = Thread(target=listen, daemon=True)
t.start()
# ## Pushing the transactions to Planetmint
# After starting the listen thread, we wait for it to connect, and then we
# proceed.
ws_ready.wait()
# Here we prepare, sign, and send ten different `CREATE` transactions. To
# make sure each transaction is different from the other, we generate a
# random `uuid`.
for _ in range(10):
tx = pm.transactions.fulfill(
pm.transactions.prepare(
operation='CREATE',
signers=alice.public_key,
asset={'data': {'uuid': str(uuid4())}}),
private_keys=alice.private_key)
# We don't want to wait for each transaction to be in a block. By using
# `async` mode, we make sure that the driver returns as soon as the
# transaction is pushed to the Planetmint API. Remember: we expect all
# transactions to be in the shared queue: this is a two phase test,
# first we send a bunch of transactions, then we check if they are
# valid (and, in this case, they should).
pm.transactions.send_async(tx)
# The `id` of every sent transaction is then stored in a list.
sent.append(tx['id'])
# ## Check the valid transactions coming from Planetmint
# Now we are ready to check if Planetmint did its job. A simple way to
# check if all sent transactions have been processed is to **remove** from
# `sent` the transactions we get from the *listen thread*. At one point in
# time, `sent` should be empty, and we exit the test.
while sent:
# To avoid waiting forever, we have an arbitrary timeout of 5
# seconds: it should be enough time for Planetmint to create
# blocks, in fact a new block is created every second. If we hit
# the timeout, then game over ¯\\\_(ツ)\_/¯
try:
event = received.get(timeout=5)
txid = json.loads(event)['transaction_id']
except queue.Empty:
assert False, 'Did not receive all expected transactions'
# Last thing is to try to remove the `txid` from the set of sent
# transactions. If this test is running in parallel with others, we
# might get a transaction id of another test, and `remove` can fail.
# It's OK if this happens.
try:
sent.remove(txid)
except ValueError:
pass

View File

@ -0,0 +1,336 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# ## Imports
import time
import json
# For this test case we need the planetmint_driver.crypto package
import base58
import sha3
from cryptoconditions import Ed25519Sha256, ThresholdSha256
from planetmint_driver.crypto import generate_keypair
# Import helper to deal with multiple nodes
from .helper.hosts import Hosts
def prepare_condition_details(condition: ThresholdSha256):
condition_details = {
'subconditions': [],
'threshold': condition.threshold,
'type': condition.TYPE_NAME
}
for s in condition.subconditions:
if (s['type'] == 'fulfillment' and s['body'].TYPE_NAME == 'ed25519-sha-256'):
condition_details['subconditions'].append({
'type': s['body'].TYPE_NAME,
'public_key': base58.b58encode(s['body'].public_key).decode()
})
else:
condition_details['subconditions'].append(prepare_condition_details(s['body']))
return condition_details
def test_threshold():
# Setup connection to test nodes
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
# Generate Keypars for Alice, Bob an Carol!
alice, bob, carol = generate_keypair(), generate_keypair(), generate_keypair()
# ## Alice and Bob create a transaction
# Alice and Bob just moved into a shared flat, no one can afford these
# high rents anymore. Bob suggests to get a dish washer for the
# kitchen. Alice agrees and here they go, creating the asset for their
# dish washer.
dw_asset = {
'data': {
'dish washer': {
'serial_number': 1337
}
}
}
# Create subfulfillments
alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))
bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))
carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))
# Create threshold condition (2/3) and add subfulfillments
threshold_sha256 = ThresholdSha256(2)
threshold_sha256.add_subfulfillment(alice_ed25519)
threshold_sha256.add_subfulfillment(bob_ed25519)
threshold_sha256.add_subfulfillment(carol_ed25519)
# Create a condition uri and details for the output object
condition_uri = threshold_sha256.condition.serialize_uri()
condition_details = prepare_condition_details(threshold_sha256)
# Assemble output and input for the handcrafted tx
output = {
'amount': '1',
'condition': {
'details': condition_details,
'uri': condition_uri,
},
'public_keys': (alice.public_key, bob.public_key, carol.public_key),
}
# The yet to be fulfilled input:
input_ = {
'fulfillment': None,
'fulfills': None,
'owners_before': (alice.public_key, bob.public_key),
}
# Assemble the handcrafted transaction
handcrafted_dw_tx = {
'operation': 'CREATE',
'asset': dw_asset,
'metadata': None,
'outputs': (output,),
'inputs': (input_,),
'version': '2.0',
'id': None,
}
# Create sha3-256 of message to sign
message = json.dumps(
handcrafted_dw_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
message = sha3.sha3_256(message.encode())
# Sign message with Alice's und Bob's private key
alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key))
bob_ed25519.sign(message.digest(), base58.b58decode(bob.private_key))
# Create fulfillment and add uri to inputs
fulfillment_threshold = ThresholdSha256(2)
fulfillment_threshold.add_subfulfillment(alice_ed25519)
fulfillment_threshold.add_subfulfillment(bob_ed25519)
fulfillment_threshold.add_subcondition(carol_ed25519.condition)
fulfillment_uri = fulfillment_threshold.serialize_uri()
handcrafted_dw_tx['inputs'][0]['fulfillment'] = fulfillment_uri
# Create tx_id for handcrafted_dw_tx and send tx commit
json_str_tx = json.dumps(
handcrafted_dw_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
dw_creation_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()
handcrafted_dw_tx['id'] = dw_creation_txid
pm.transactions.send_commit(handcrafted_dw_tx)
time.sleep(1)
# Assert that the tx is propagated to all nodes
hosts.assert_transaction(dw_creation_txid)
def test_weighted_threshold():
hosts = Hosts('/shared/hostnames')
pm = hosts.get_connection()
alice, bob, carol = generate_keypair(), generate_keypair(), generate_keypair()
asset = {
'data': {
'trashcan': {
'animals': ['racoon_1', 'racoon_2']
}
}
}
alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))
bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))
carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))
threshold = ThresholdSha256(1)
threshold.add_subfulfillment(alice_ed25519)
sub_threshold = ThresholdSha256(2)
sub_threshold.add_subfulfillment(bob_ed25519)
sub_threshold.add_subfulfillment(carol_ed25519)
threshold.add_subfulfillment(sub_threshold)
condition_uri = threshold.condition.serialize_uri()
condition_details = prepare_condition_details(threshold)
# Assemble output and input for the handcrafted tx
output = {
'amount': '1',
'condition': {
'details': condition_details,
'uri': condition_uri,
},
'public_keys': (alice.public_key, bob.public_key, carol.public_key),
}
# The yet to be fulfilled input:
input_ = {
'fulfillment': None,
'fulfills': None,
'owners_before': (alice.public_key, bob.public_key),
}
# Assemble the handcrafted transaction
handcrafted_tx = {
'operation': 'CREATE',
'asset': asset,
'metadata': None,
'outputs': (output,),
'inputs': (input_,),
'version': '2.0',
'id': None,
}
# Create sha3-256 of message to sign
message = json.dumps(
handcrafted_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
message = sha3.sha3_256(message.encode())
# Sign message with Alice's und Bob's private key
alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key))
# Create fulfillment and add uri to inputs
sub_fulfillment_threshold = ThresholdSha256(2)
sub_fulfillment_threshold.add_subcondition(bob_ed25519.condition)
sub_fulfillment_threshold.add_subcondition(carol_ed25519.condition)
fulfillment_threshold = ThresholdSha256(1)
fulfillment_threshold.add_subfulfillment(alice_ed25519)
fulfillment_threshold.add_subfulfillment(sub_fulfillment_threshold)
fulfillment_uri = fulfillment_threshold.serialize_uri()
handcrafted_tx['inputs'][0]['fulfillment'] = fulfillment_uri
# Create tx_id for handcrafted_dw_tx and send tx commit
json_str_tx = json.dumps(
handcrafted_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
creation_tx_id = sha3.sha3_256(json_str_tx.encode()).hexdigest()
handcrafted_tx['id'] = creation_tx_id
pm.transactions.send_commit(handcrafted_tx)
time.sleep(1)
# Assert that the tx is propagated to all nodes
hosts.assert_transaction(creation_tx_id)
# Now transfer created asset
alice_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))
bob_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))
carol_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))
transfer_condition_uri = alice_transfer_ed25519.condition.serialize_uri()
# Assemble output and input for the handcrafted tx
transfer_output = {
'amount': '1',
'condition': {
'details': {
'type': alice_transfer_ed25519.TYPE_NAME,
'public_key': base58.b58encode(alice_transfer_ed25519.public_key).decode()
},
'uri': transfer_condition_uri,
},
'public_keys': (alice.public_key,),
}
# The yet to be fulfilled input:
transfer_input_ = {
'fulfillment': None,
'fulfills': {
'transaction_id': creation_tx_id,
'output_index': 0
},
'owners_before': (alice.public_key, bob.public_key, carol.public_key),
}
# Assemble the handcrafted transaction
handcrafted_transfer_tx = {
'operation': 'TRANSFER',
'asset': {'id': creation_tx_id},
'metadata': None,
'outputs': (transfer_output,),
'inputs': (transfer_input_,),
'version': '2.0',
'id': None,
}
# Create sha3-256 of message to sign
message = json.dumps(
handcrafted_transfer_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
message = sha3.sha3_256(message.encode())
message.update('{}{}'.format(
handcrafted_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
handcrafted_transfer_tx['inputs'][0]['fulfills']['output_index']).encode())
# Sign message with Alice's und Bob's private key
bob_transfer_ed25519.sign(message.digest(), base58.b58decode(bob.private_key))
carol_transfer_ed25519.sign(message.digest(), base58.b58decode(carol.private_key))
sub_fulfillment_threshold = ThresholdSha256(2)
sub_fulfillment_threshold.add_subfulfillment(bob_transfer_ed25519)
sub_fulfillment_threshold.add_subfulfillment(carol_transfer_ed25519)
# Create fulfillment and add uri to inputs
fulfillment_threshold = ThresholdSha256(1)
fulfillment_threshold.add_subcondition(alice_transfer_ed25519.condition)
fulfillment_threshold.add_subfulfillment(sub_fulfillment_threshold)
fulfillment_uri = fulfillment_threshold.serialize_uri()
handcrafted_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri
# Create tx_id for handcrafted_dw_tx and send tx commit
json_str_tx = json.dumps(
handcrafted_transfer_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
transfer_tx_id = sha3.sha3_256(json_str_tx.encode()).hexdigest()
handcrafted_transfer_tx['id'] = transfer_tx_id
pm.transactions.send_commit(handcrafted_transfer_tx)
time.sleep(1)
# Assert that the tx is propagated to all nodes
hosts.assert_transaction(transfer_tx_id)

View File

@ -0,0 +1,84 @@
# GOAL:
# In this script I tried to implement the ECDSA signature using zenroom
# However, the scripts are customizable and so with the same procedure
# we can implement more complex smart contracts
# PUBLIC IDENTITY
# The public identity of the users in this script (Bob and Alice)
# is the pair (ECDH public key, Testnet address)
import json
from cryptoconditions import ZenroomSha256
from json.decoder import JSONDecodeError
def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode,
condition_script_zencode, zenroom_data, zenroom_house_assets):
alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'),
keys={'keys': alice}).output)
zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'),
keys={'keys': bob}).output))
# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer
zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data)
# CRYPTO-CONDITIONS: generate the condition uri
condition_uri = zenSha.condition.serialize_uri()
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
unsigned_fulfillment_dict = {
'type': zenSha.TYPE_NAME,
'script': fulfill_script_zencode,
'keys': zen_public_keys,
}
output = {
'amount': '1000',
'condition': {
'details': unsigned_fulfillment_dict,
'uri': condition_uri,
},
'data': zenroom_data,
'script': fulfill_script_zencode,
'conf': '',
'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ),
}
input_ = {
'fulfillment': None,
'fulfills': None,
'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ),
}
token_creation_tx = {
'operation': 'CREATE',
'asset': zenroom_house_assets,
'metadata': None,
'outputs': (output,),
'inputs': (input_,),
'version': '2.0',
'id': None,
}
# JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps(
token_creation_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)
try:
assert(not zenSha.validate(message=message))
except JSONDecodeError:
pass
except ValueError:
pass
message = zenSha.sign(message, condition_script_zencode, alice)
assert(zenSha.validate(message=message))

View File

@ -16,12 +16,17 @@ show_validator () {
# Elect new voting power for node
elect_validator () {
planetmint election new upsert-validator $1 $2 $3 --private-key /tendermint/config/priv_validator_key.json
planetmint election new upsert-validator $1 $2 $3 --private-key /tendermint/config/priv_validator_key.json 2>&1
}
# Propose new chain migration
propose_migration () {
planetmint election new chain-migration --private-key /tendermint/config/priv_validator_key.json 2>&1
}
# Show election state
show_election () {
planetmint election show $1
planetmint election show $1 2>&1
}
# Approve election
@ -33,7 +38,13 @@ approve_validator () {
elect () {
node_id=$(show_id)
validator_pubkey=$(show_validator | jq -r .value)
proposal=$(elect_validator $validator_pubkey $1 $node_id 2>&1 | grep SUCCESS)
proposal=$(elect_validator $validator_pubkey $1 $node_id | grep SUCCESS)
echo ${proposal##* }
}
# Create chain migration proposal and return election id
migrate () {
proposal=$(propose_migration | grep SUCCESS)
echo ${proposal##* }
}
@ -50,6 +61,9 @@ while [ "$1" != "" ]; do
elect ) shift
elect $1
;;
migrate ) shift
migrate
;;
show_election ) shift
show_election $1
;;

View File

@ -4,19 +4,21 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
# Read host names from shared
readarray -t HOSTNAMES < /shared/hostnames
# Start CLI Tests
# Split into proposer and approvers
ALPHA=${HOSTNAMES[0]}
BETAS=${HOSTNAMES[@]:1}
# Test upsert new validator
/tests/upsert-new-validator.sh
# Propose validator upsert
result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${ALPHA} 'bash -s' < scripts/election.sh elect 2)
# Test chain migration
# TODO: implementation not finished
#/tests/chain-migration.sh
# Approve validator upsert
for BETA in ${BETAS[@]}; do
ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${BETA} 'bash -s' < scripts/election.sh approve $result
done
# TODO: Implement test for voting edge cases or implicit in chain migration and upsert validator?
exitcode=$?
if [ $exitcode -ne 0 ]; then
exit $exitcode
fi
exec "$@"