Смарт-контракты Docker с использованием REST API ноды

Подсказка

Техническое описание особенностей реализации контрактов приведено в разделе Смарт-контракты Docker.

Описание логики программы

В разделе рассматривается пример создания и запуска простого смарт-контракта. Контракт выполняет инкремент переданного на вход числа при каждой call-транзакции.

Листинг программы contract.py на Python:

import json
import os
import requests
import sys


def find_param_value(params, name):
    for param in params:
        if param['key'] == name: return param['value']
    return None


def print_success(results):
    print(json.dumps(results, separators=(',', ':')))


def print_error(message):
    print(message)
    sys.exit(3)


def get_value(contract_id):
    node = os.environ['NODE_API']
    if not node:
        print_error("Node REST API address is not defined")
    token = os.environ["API_TOKEN"]
    if not token:
        print_error("Node API token is not defined")
    headers = {'X-Contract-Api-Token': token}
    url = '{0}/internal/contracts/{1}/sum'.format(node, contract_id)
    r = requests.get(url, verify=False, timeout=2, headers=headers)
    data = r.json()
    return data['value']


if __name__ == '__main__':
    command = os.environ['COMMAND']
    if command == 'CALL':
        contract_id = json.loads(os.environ['TX'])['contractId']
        value = get_value(contract_id)
        print_success([{
            "key": "sum",
            "type": "integer",
            "value": value + 1}])
    elif command == 'CREATE':
        print_success([{
            "key": "sum",
            "type": "integer",
            "value": 0}])
    else:
        print_error("Unknown command {0}".format(command))

Описание работы

  • Программа ожидает получить структуру данных в формате json с полем «params».

  • Считывает значение поля «а».

  • Возвращает результат в виде значения поля «{a}+1» в формате json.

Пример входящих параметров

"params":[
    {
        "key":"a",
        "type":"integer",
        "value":1
    }
]

Установка смарт-контракта

  1. Скачать и установить Docker for Developers для вашей операционной системы.

  2. Подготовить образ контракта. В папке stateful-increment-contract создать следующие файлы:

  • contract.py

  • Dockerfile

  • run.sh

Листинг файла run.sh

#!/bin/sh

python contract.py

Листинг файла Dockerfile

FROM python:alpine3.8
ADD contract.py /
ADD run.sh /
RUN chmod +x run.sh
RUN apk add --no-cache --update iptables
CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"

Важно

В контейнер со смарт-контрактом необходимо установить iptables.

  1. Установить образ в Docker registry. Выполнить в терминале следующие команды:

docker run -d -p 5000:5000 --name registry registry:2
cd contracts/stateful-increment-contract
docker build -t stateful-increment-contract .
docker image tag stateful-increment-contract localhost:5000/stateful-increment-contract
docker start registry
docker push localhost:5000/stateful-increment-contract
  1. Для получения информации о контейнере выполнить в терминале следующую команду:

docker inspect 57c2c2d2643d
[
{
    "Id": "sha256:57c2c2d2643da042ef8dd80010632ffdd11e3d2e3f85c20c31dce838073614dd",
    "RepoTags": [
        "wenode:latest"
    ],
    "RepoDigests": [],
    "Parent": "sha256:d91d2307057bf3bb5bd9d364f16cd3d7eda3b58edf2686e1944bcc7133f07913",
    "Comment": "",
    "Created": "2019-10-25T14:15:03.856072509Z",
    "Container": "",
    "ContainerConfig": {
        "Hostname": "",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,

Идентификатор смарт-контракта Id является значением поля imageHash для использования в транзакциях с созданным смарт-контрактом.

  1. Подписать транзакцию на создание смарт-контракта. В рассматриваемом примере транзакция подписывается ключом, сохраненным в keystore ноды.

Подсказка

Для создания ключевой пары и адреса участника используется утилита generators.jar. Порядок действий создания ключевой пары приведен в подразделе Генерирование ключевых пар. Правила формирования запросов к ноде приведены в разделе REST API ноды.

Тело запроса

{
  "fee": 100000000,
  "image": "stateful-increment-contract:latest",
  "imageHash": "7d3b915c82930dd79591aab040657338f64e5d8b842abe2d73d5c8f828584b65",
  "contractName": "stateful-increment-contract",
  "sender": "3PudkbvjV1nPj1TkuuRahh4sGdgfr4YAUV2",
  "password": "",
  "params": [],
  "type": 103,
  "version": 1
 }

Пример запроса

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'X-Contract-Api-Token' -d '    { \
        "fee": 100000000, \
        "image": "stateful-increment-contract:latest", \
        "imageHash": "7d3b915c82930dd79591aab040657338f64e5d8b842abe2d73d5c8f828584b65", \
        "contractName": "stateful-increment-contract", \
        "sender": "3PudkbvjV1nPj1TkuuRahh4sGdgfr4YAUV2", \
        "password": "", \
        "params": [], \
        "type": 103, \
        "version": 1 \
    }' 'http://localhost:6862/transactions/sign'

Пример ответа

{
    "type": 103,
    "id": "ULcq9R7PvUB2yPMrmBdxoTi3bcRmQPT3JDLLLZVj4Ky",
    "sender": "3N3YTj1tNwn8XUJ8ptGKbPuEFNa9GFnhqew",
    "senderPublicKey": "3kW7vy6nPC59BXM67n5N56rhhAv38Dws5skqDsjMVT2M",
    "fee": 500000,
    "timestamp": 1550591678479,
    "proofs": [ "yecRFZm9iBLyDy93bDVaNo1PR5Qkkic7196GAgUt9TNH1cnQphq4yGQQ8Fxj4BYA4TaqYVw5qxtWzGMPQyVeKYv" ],
    "version": 1,
    "image": "stateful-increment-contract:latest",
    "imageHash": "7d3b915c82930dd79591aab040657338f64e5d8b842abe2d73d5c8f828584b65",
    "contractName": "stateful-increment-contract",
    "params": [],
    "height": 1619
}
  1. Отправить подписанную транзакцию в блокчейн. Ответ от метода sign необходимо передать на вход для метода broadcast.

Пример запроса

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'X-Contract-Api-Token' -d '{ \
{
    "type": 103, \
    "id": "ULcq9R7PvUB2yPMrmBdxoTi3bcRmQPT3JDLLLZVj4Ky", \
    "sender": "3N3YTj1tNwn8XUJ8ptGKbPuEFNa9GFnhqew", \
    "senderPublicKey": "3kW7vy6nPC59BXM67n5N56rhhAv38Dws5skqDsjMVT2M", \
    "fee": 500000, \
    "timestamp": 1550591678479, \
    "proofs": [ "yecRFZm9iBLyDy93bDVaNo1PR5Qkkic7196GAgUt9TNH1cnQphq4yGQQ8Fxj4BYA4TaqYVw5qxtWzGMPQyVeKYv" ], \
    "version": 1, \
    "image": "stateful-increment-contract:latest", \
    "imageHash": "7d3b915c82930dd79591aab040657338f64e5d8b842abe2d73d5c8f828584b65", \
    "contractName": "stateful-increment-contract", \
    "params": [], \
    "height": 1619 \
}
}' 'http://localhost:6862/transactions/broadcast'
  1. По id транзакции убедиться, что транзакция с инициализацией контракта размещена в блокчейне.

Пример ответа

{
    "type": 103,
    "id": "ULcq9R7PvUB2yPMrmBdxoTi3bcRmQPT3JDLLLZVj4Ky",
    "sender": "3N3YTj1tNwn8XUJ8ptGKbPuEFNa9GFnhqew",
    "senderPublicKey": "3kW7vy6nPC59BXM67n5N56rhhAv38Dws5skqDsjMVT2M",
    "fee": 500000,
    "timestamp": 1550591678479,
    "proofs": [ "yecRFZm9iBLyDy93bDVaNo1PR5Qkkic7196GAgUt9TNH1cnQphq4yGQQ8Fxj4BYA4TaqYVw5qxtWzGMPQyVeKYv" ],
    "version": 1,
    "image": "stateful-increment-contract:latest",
    "imageHash": "7d3b915c82930dd79591aab040657338f64e5d8b842abe2d73d5c8f828584b65",
    "contractName": "stateful-increment-contract",
    "params": [],
    "height": 1619
}

Исполнение смарт-контракта

  1. Подписать call-транзакцию на вызов (исполнение) смарт-контракта.

В поле contractId указать идентификатор транзакции инициализации контракта.

Тело запроса

{
    "contractId": "2sqPS2VAKmK77FoNakw1VtDTCbDSa7nqh5wTXvJeYGo2",
    "fee": 10,
    "sender": "3PKyW5FSn4fmdrLcUnDMRHVyoDBxybRgP58",
    "password": "",
    "type": 104,
    "version": 1,
    "params": [
        {
            "type": "integer",
            "key": "a",
            "value": 1
        }
    ]
}

Пример запроса

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'X-Contract-Api-Token' -d '{ \
    "contractId": "2sqPS2VAKmK77FoNakw1VtDTCbDSa7nqh5wTXvJeYGo2", \
    "fee": 10, \
    "sender": "3PKyW5FSn4fmdrLcUnDMRHVyoDBxybRgP58", \
    "password": "", \
    "type": 104, \
    "version": 1, \
    "params": [ \
        { \
            "type": "integer", \
            "key": "a", \
            "value": 1 \
        } \
    ] \
}' 'http://localhost:6862/transactions/sign'

Пример ответа

{
    "type": 104,
    "id": "9fBrL2n5TN473g1gNfoZqaAqAsAJCuHRHYxZpLexL3VP",
    "sender": "3PKyW5FSn4fmdrLcUnDMRHVyoDBxybRgP58",
    "senderPublicKey": "2YvzcVLrqLCqouVrFZynjfotEuPNV9GrdauNpgdWXLsq",
    "fee": 10,
    "timestamp": 1549365736923,
    "proofs": [
        "2q4cTBhDkEDkFxr7iYaHPAv1dzaKo5rDaTxPF5VHryyYTXxTPvN9Wb3YrsDYixKiUPXBnAyXzEcnKPFRCW9xVp4v"
    ],
    "version": 1,
    "contractId": "2sqPS2VAKmK77FoNakw1VtDTCbDSa7nqh5wTXvJeYGo2",
    "params": [
        {
        "key": "a",
        "type": "integer",
        "value": 1
        }
    ]
}
  1. Отправить подписанную транзакцию в блокчейн. Ответ от метода sign необходимо передать на вход для метода broadcast.

Пример запроса

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'X-Contract-Api-Token' -d '{ \
"type": 104, \
"id": "9fBrL2n5TN473g1gNfoZqaAqAsAJCuHRHYxZpLexL3VP", \
"sender": "3PKyW5FSn4fmdrLcUnDMRHVyoDBxybRgP58", \
"senderPublicKey": "2YvzcVLrqLCqouVrFZynjfotEuPNV9GrdauNpgdWXLsq", \
"fee": 10, \
"timestamp": 1549365736923, \
"proofs": [ \
    "2q4cTBhDkEDkFxr7iYaHPAv1dzaKo5rDaTxPF5VHryyYTXxTPvN9Wb3YrsDYixKiUPXBnAyXzEcnKPFRCW9xVp4v" \
], \
"version": 1, \
"contractId": "2sqPS2VAKmK77FoNakw1VtDTCbDSa7nqh5wTXvJeYGo2", \
"params": [ \
    { \
    "key": "a", \
    "type": "integer", \
    "value": 1 \
    } \
] \
}' 'http://localhost:6862/transactions/broadcast'
  1. Получить результат выполнения смарт-контракта по его идентификатору.

Пример ответа

[
    {
        "key": "1+1",
        "type": "integer",
        "value": 2
    }
]