mirror of
https://github.com/planetmint/planetmint.git
synced 2025-06-06 22:26:42 +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:
|
volumes:
|
||||||
- ./integration/python/src:/src
|
- ./integration/python/src:/src
|
||||||
- ./integration/scripts:/scripts
|
- ./integration/scripts:/scripts
|
||||||
|
- ./integration/cli:/tests
|
||||||
- shared:/shared
|
- shared:/shared
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -83,18 +83,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PLANETMINT_ENDPOINT=planetmint
|
- 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
|
# Build docs only
|
||||||
# docker-compose build bdocs
|
# docker-compose build bdocs
|
||||||
# docker-compose up -d 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
|
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 mkdir -p /src
|
||||||
RUN pip install --upgrade \
|
RUN pip install --upgrade \
|
||||||
pytest~=6.2.5 \
|
pytest~=6.2.5 \
|
||||||
planetmint-driver~=0.9.0 \
|
planetmint-driver~=0.9.0 \
|
||||||
pycco
|
pycco \
|
||||||
|
websocket-client~=0.47.0 \
|
||||||
RUN apt-get update && apt-get install -y openssh-client openssh-server
|
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
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
# import Planetmint and create object
|
# import Planetmint and create object
|
||||||
from planetmint_driver import Planetmint
|
|
||||||
from planetmint_driver.crypto import generate_keypair
|
from planetmint_driver.crypto import generate_keypair
|
||||||
|
|
||||||
|
# import helper to manage multiple nodes
|
||||||
|
from .helper.hosts import Hosts
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def test_basic():
|
def test_basic():
|
||||||
# Setup up connection to Planetmint integration test nodes
|
# Setup up connection to Planetmint integration test nodes
|
||||||
hosts = []
|
hosts = Hosts('/shared/hostnames')
|
||||||
with open('/shared/hostnames') as f:
|
pm_alpha = hosts.get_connection()
|
||||||
hosts = f.readlines()
|
|
||||||
|
|
||||||
pm_hosts = list(map(lambda x: Planetmint(x), hosts))
|
|
||||||
|
|
||||||
pm_alpha = pm_hosts[0]
|
|
||||||
pm_betas = pm_hosts[1:]
|
|
||||||
|
|
||||||
# genarate a keypair
|
# genarate a keypair
|
||||||
alice = generate_keypair()
|
alice = generate_keypair()
|
||||||
@ -50,13 +47,8 @@ def test_basic():
|
|||||||
|
|
||||||
creation_tx_id = fulfilled_creation_tx['id']
|
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
|
# Assert that transaction is stored on all planetmint nodes
|
||||||
for tx in creation_tx_betas:
|
hosts.assert_transaction(creation_tx_id)
|
||||||
assert creation_tx_alpha == tx
|
|
||||||
|
|
||||||
# Transfer
|
# Transfer
|
||||||
# create the output and inout for the transaction
|
# create the output and inout for the transaction
|
||||||
@ -87,10 +79,5 @@ def test_basic():
|
|||||||
|
|
||||||
transfer_tx_id = sent_transfer_tx['id']
|
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
|
# Assert that transaction is stored on both planetmint nodes
|
||||||
for tx in transfer_tx_betas:
|
hosts.assert_transaction(transfer_tx_id)
|
||||||
assert transfer_tx_alpha == tx
|
|
||||||
|
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
|
import time
|
||||||
|
|
||||||
# For this test case we need import and use the Python driver
|
# For this test case we need import and use the Python driver
|
||||||
from planetmint_driver import Planetmint
|
|
||||||
from planetmint_driver.crypto import generate_keypair
|
from planetmint_driver.crypto import generate_keypair
|
||||||
|
|
||||||
|
# Import helper to deal with multiple nodes
|
||||||
|
from .helper.hosts import Hosts
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_owners():
|
def test_multiple_owners():
|
||||||
# Setup up connection to Planetmint integration test nodes
|
# Setup up connection to Planetmint integration test nodes
|
||||||
hosts = []
|
hosts = Hosts('/shared/hostnames')
|
||||||
with open('/shared/hostnames') as f:
|
pm_alpha = hosts.get_connection()
|
||||||
hosts = f.readlines()
|
|
||||||
|
|
||||||
pm_hosts = list(map(lambda x: Planetmint(x), hosts))
|
|
||||||
|
|
||||||
pm_alpha = pm_hosts[0]
|
|
||||||
pm_betas = pm_hosts[1:]
|
|
||||||
|
|
||||||
# Generate Keypairs for Alice and Bob!
|
# Generate Keypairs for Alice and Bob!
|
||||||
alice, bob = generate_keypair(), generate_keypair()
|
alice, bob = generate_keypair(), generate_keypair()
|
||||||
@ -73,13 +69,9 @@ def test_multiple_owners():
|
|||||||
dw_id = fulfilled_dw_tx['id']
|
dw_id = fulfilled_dw_tx['id']
|
||||||
|
|
||||||
time.sleep(1)
|
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
|
# Use hosts to assert that the transaction is properly propagated to every node
|
||||||
for tx in pm_betas_tx:
|
hosts.assert_transaction(dw_id)
|
||||||
assert pm_alpha_tx == tx
|
|
||||||
|
|
||||||
# Let's check if the transaction was successful.
|
# Let's check if the transaction was successful.
|
||||||
assert pm_alpha.transactions.retrieve(dw_id), \
|
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)
|
sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx)
|
||||||
time.sleep(1)
|
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
|
# Now compare if both nodes returned the same transaction
|
||||||
for tx in pm_betas_tx:
|
hosts.assert_transaction(fulfilled_transfer_tx['id'])
|
||||||
assert pm_alpha_tx == tx
|
|
||||||
|
|
||||||
# They check if the transaction was successful.
|
# They check if the transaction was successful.
|
||||||
assert pm_alpha.transactions.retrieve(
|
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 new voting power for node
|
||||||
elect_validator () {
|
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 state
|
||||||
show_election () {
|
show_election () {
|
||||||
planetmint election show $1
|
planetmint election show $1 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Approve election
|
# Approve election
|
||||||
@ -33,7 +38,13 @@ approve_validator () {
|
|||||||
elect () {
|
elect () {
|
||||||
node_id=$(show_id)
|
node_id=$(show_id)
|
||||||
validator_pubkey=$(show_validator | jq -r .value)
|
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##* }
|
echo ${proposal##* }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +61,9 @@ while [ "$1" != "" ]; do
|
|||||||
elect ) shift
|
elect ) shift
|
||||||
elect $1
|
elect $1
|
||||||
;;
|
;;
|
||||||
|
migrate ) shift
|
||||||
|
migrate
|
||||||
|
;;
|
||||||
show_election ) shift
|
show_election ) shift
|
||||||
show_election $1
|
show_election $1
|
||||||
;;
|
;;
|
||||||
|
@ -4,19 +4,21 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
# Read host names from shared
|
# Start CLI Tests
|
||||||
readarray -t HOSTNAMES < /shared/hostnames
|
|
||||||
|
|
||||||
# Split into proposer and approvers
|
# Test upsert new validator
|
||||||
ALPHA=${HOSTNAMES[0]}
|
/tests/upsert-new-validator.sh
|
||||||
BETAS=${HOSTNAMES[@]:1}
|
|
||||||
|
|
||||||
# Propose validator upsert
|
# Test chain migration
|
||||||
result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${ALPHA} 'bash -s' < scripts/election.sh elect 2)
|
# TODO: implementation not finished
|
||||||
|
#/tests/chain-migration.sh
|
||||||
|
|
||||||
# Approve validator upsert
|
# TODO: Implement test for voting edge cases or implicit in chain migration and upsert validator?
|
||||||
for BETA in ${BETAS[@]}; do
|
|
||||||
ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${BETA} 'bash -s' < scripts/election.sh approve $result
|
exitcode=$?
|
||||||
done
|
|
||||||
|
if [ $exitcode -ne 0 ]; then
|
||||||
|
exit $exitcode
|
||||||
|
fi
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
Loading…
x
Reference in New Issue
Block a user