mirror of
https://github.com/planetmint/planetmint.git
synced 2025-06-05 21:56:44 +00:00
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:
parent
f9f1a64773
commit
df7c1e1ccf
@ -46,6 +46,7 @@ services:
|
||||
volumes:
|
||||
- ./integration/python/src:/src
|
||||
- ./integration/scripts:/scripts
|
||||
- ./integration/cli:/tests
|
||||
- shared:/shared
|
||||
|
||||
volumes:
|
||||
|
@ -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
|
||||
|
47
integration/cli/chain-migration.sh
Executable file
47
integration/cli/chain-migration.sh
Executable 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=}
|
33
integration/cli/upsert-new-validator.sh
Executable file
33
integration/cli/upsert-new-validator.sh
Executable 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
|
@ -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
|
0
integration/python/src/__init__.py
Normal file
0
integration/python/src/__init__.py
Normal file
95
integration/python/src/conftest.py
Normal file
95
integration/python/src/conftest.py
Normal 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
|
0
integration/python/src/helper/__init__.py
Normal file
0
integration/python/src/helper/__init__.py
Normal file
36
integration/python/src/helper/hosts.py
Normal file
36
integration/python/src/helper/hosts.py
Normal 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)
|
@ -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)
|
||||
|
183
integration/python/src/test_divisible_asset.py
Normal file
183
integration/python/src/test_divisible_asset.py
Normal 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 :)
|
48
integration/python/src/test_double_spend.py
Normal file
48
integration/python/src/test_double_spend.py
Normal 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
|
@ -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(
|
100
integration/python/src/test_naughty_strings.py
Normal file
100
integration/python/src/test_naughty_strings.py
Normal 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)
|
131
integration/python/src/test_stream.py
Normal file
131
integration/python/src/test_stream.py
Normal 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
|
336
integration/python/src/test_threshold.py
Normal file
336
integration/python/src/test_threshold.py
Normal 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)
|
84
integration/python/src/test_zenroom.py
Normal file
84
integration/python/src/test_zenroom.py
Normal 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))
|
@ -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
|
||||
;;
|
||||
|
@ -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 "$@"
|
Loading…
x
Reference in New Issue
Block a user