Использование JSON-RPC в Python для Monero
Monero — это безопасная, приватная, неотслеживаемая криптовалюта. Для более подробной информации можете посетить getmonero.org и r/Monero.
Основными компонентами Monero являются
simplewallet
иbitmonerod
. Первый — это кошелёк, что понятно из названия. Второй — демон, отвечающий за взаимодействие с блокчейном Monero.
Важнейшие функции simplewallet
и bitmonerod
могут выполняться посредством протокола JSON-RPC (JavaScript Object Notation Remote Procedure Calls – JSON-вызов удалённых процедур).
Используя эти процедуры, можно разрабатывать приложения на базе bitmonerod
и simplewallet
, например, GUI-кошельки, веб-приложения, позволяющие просматривать баланс, обозреватели блокчейна.
Ввиду того, что в сети практически нет инструкций и примеров использования JSON-RPC для взаимодействия с bitmonerod
и simplewallet
, предлагаю вам ознакомиться с примерами на языке Python. Надюсь, они помогут вам начать создавать приложения на базе Monero.
simplewallet
Примеры демонстрируют вызов наиболее востребованных процедур, предоставляемых simplewallet
, это:
getbalance
query_key
get_payments
getaddress
incoming_transfers
transfer
С базовой документацией по этим процедурам можно ознакомиться здесь.
Необходимые условия
Перед запуском кода примеров убедитесь в том, что simplewallet
запущен и настроен на приём входящих RPC-запросов. Для этого вы можете запустить simplewallet
со следующимим параметрами:
/opt/bitmonero/simplewallet --wallet-file ~/wallet.bin --password --rpc-bind-port 18082
Код был написан и протестирован на Ubuntu 15.10 и Python 3.4.3 с установленным пакетом Requests.
Пример 1: получение баланса кошелька
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# simplewallet' procedure/method to call
rpc_input = {
"method": "getbalance"
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# amounts in cryptonote are encoded in a way which is convenient
# for a computer, not a user. Thus, its better need to recode them
# to something user friendly, before displaying them.
#
# For examples:
# 4760000000000 is 4.76
# 80000000000 is 0.08
#
# In example 3 "Basic example 3: get incoming transfers" it is
# shown how to convert cryptonote values to user friendly format.
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"jsonrpc": "2.0",
"id": "0",
"result": {
"unlocked_balance": 4760000000000,
"balance": 4760000000000
}
}
Пример 2: получение информации о платеже по его идентификатору
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# an example of a payment id
payment_id = "426870cb29c598e191184fa87003ca562d9e25f761ee9e520a888aec95195912"
# simplewallet' procedure/method to call
rpc_input = {
"method": "get_payments",
"params": {"payment_id": payment_id}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"result": {
"payments": [
{
"tx_hash": "66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577",
"amount": 4800000000000,
"block_height": 795523,
"payment_id": "426870cb29c598e191184fa87003ca562d9e25f761ee9e520a888aec95195912",
"unlock_time": 0
}
]
},
"jsonrpc": "2.0",
"id": "0"
}
Пример 3: получение списка входящих переводов
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# simplewallet' procedure/method to call
rpc_input = {
"method": "incoming_transfers",
"params": {"transfer_type": "all"}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# make json dict with response
response_json = response.json()
# amounts in cryptonote are encoded in a way which is convenient
# for a computer, not a user. Thus, its better need to recode them
# to something user friendly, before displaying them.
#
# For examples:
# 4760000000000 is 4.76
# 80000000000 is 0.08
#
if "result" in response_json:
if "transfers" in response_json["result"]:
for transfer in response_json["result"]["transfers"]:
transfer["amount"] = float(get_money(str(transfer["amount"])))
# pretty print json output
print(json.dumps(response_json, indent=4))
def get_money(amount):
"""decode cryptonote amount format to user friendly format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
s = amount
if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1:
# add some trailing zeros, if needed, to have constant width
s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s
idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT
s = s[0:idx] + "." + s[idx:]
return s
if __name__ == "__main__":
main()
Результат выполнения:
{
"jsonrpc": "2.0",
"result": {
"transfers": [
{
"tx_hash": "<66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577>",
"tx_size": 521,
"spent": true,
"global_index": 346865,
"amount": 0.8
},
{
"tx_hash": "<66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577>",
"tx_size": 521,
"spent": true,
"global_index": 177947,
"amount": 4.0
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 165782,
"amount": 0.08
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 300597,
"amount": 0.9
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 214803,
"amount": 3.0
},
{
"tx_hash": "<e8409a93edeed9f6c67e6716bb180d9593e8beafa63d51facf68bee233bf694d>",
"tx_size": 525,
"spent": false,
"global_index": 165783,
"amount": 0.08
},
{
"tx_hash": "<e8409a93edeed9f6c67e6716bb180d9593e8beafa63d51facf68bee233bf694d>",
"tx_size": 525,
"spent": false,
"global_index": 375952,
"amount": 0.7
}
]
},
"id": "0"
}
Пример 4: создание транзакции
import requests
import json
import os
import binascii
def main():
"""DONT RUN IT without changing the destination address!!!"""
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
destination_address = "489MAxaT7xXP3Etjk2suJT1uDYZU6cqFycsau2ynCTBacncWVEwe9eYFrAD6BqTn4Y2KMs7maX75iX1UFwnJNG5G88wxKoj"
# amount of xmr to send
amount = 0.54321
# cryptonote amount format is different then
# that normally used by people.
# thus the float amount must be changed to
# something that cryptonote understands
int_amount = int(get_amount(amount))
# just to make sure that amount->coversion->back
# gives the same amount as in the initial number
assert amount == float(get_money(str(int_amount))), "Amount conversion failed"
# send specified xmr amount to the given destination_address
recipents = [{"address": destination_address,
"amount": int_amount}]
# using given mixin
mixin = 4
# get some random payment_id
payment_id = get_payment_id()
# simplewallet' procedure/method to call
rpc_input = {
"method": "transfer",
"params": {"destinations": recipents,
"mixin": mixin,
"payment_id" : payment_id}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# print the payment_id
print("#payment_id: ", payment_id)
# pretty print json output
print(json.dumps(response.json(), indent=4))
def get_amount(amount):
"""encode amount (float number) to the cryptonote format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L211
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
str_amount = str(amount)
fraction_size = 0
if '.' in str_amount:
point_index = str_amount.index('.')
fraction_size = len(str_amount) - point_index - 1
while fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT and '0' == str_amount[-1]:
print(44)
str_amount = str_amount[:-1]
fraction_size = fraction_size - 1
if CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size:
return False
str_amount = str_amount[:point_index] + str_amount[point_index+1:]
if not str_amount:
return False
if fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT:
str_amount = str_amount + '0'*(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size)
return str_amount
def get_money(amount):
"""decode cryptonote amount format to user friendly format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
s = amount
if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1:
# add some trailing zeros, if needed, to have constant width
s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s
idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT
s = s[0:idx] + "." + s[idx:]
return s
def get_payment_id():
"""generate random payment_id
generate some random payment_id for the
transactions
payment_id is 32 bytes (64 hexadecimal characters)
thus we first generate 32 random byte array
which is then change to string representation, since
json will not not what to do with the byte array.
"""
random_32_bytes = os.urandom(32)
payment_id = "".join(map(chr, binascii.hexlify(random_32_bytes)))
return payment_id
if __name__ == "__main__":
main()
Результат выполнения:
#payment_id: 4926869b6b5d50b24cb59f08fd76826cacdf76201b2d4648578fe610af7f786e
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"tx_key": "",
"tx_hash": "<04764ab4855b8a9f9c42d99e19e1c40956a502260123521ca3f6488dd809797a>"
}
}
Другие примеры вы можете посмотреть здесь.
bitmonerod
bitmonerod
принимает следующие базовые RPC-запросы:
getheight
query_key
mining_status
getlastblockheader
getblockheaderbyhash
getblockheaderbyheight
getblock
get_info
get_connections
Необходимые условия
Перед запуском кода примеров убедитесь в том, что bitmonerod
запущен. Требования к среде остаются прежними (Ubuntu 15.10 и Python 3.4.3 с установленным пакетом Requests).
Пример 1: статус майнинга
import requests
import json
def main():
# bitmonerod' is running on the localhost and port of 18081
url = "http://localhost:18081/mining_status"
# standard json header
headers = {'content-type': 'application/json'}
# execute the rpc request
response = requests.post(
url,
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"status": "OK",
"threads_count": 2,
"speed": 117,
"active": true,
"address": "48daf1rG3hE1Txapcsxh6WXNe9MLNKtu7W7tKTivtSoVLHErYzvdcpea2nSTgGkz66RFP4GKVAsTV14v6G3oddBTHfxP6tU"
}
Пример 2: получить заголовок блока зная его хеш
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18081
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# the block to get
block_hash = 'd78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6'
# bitmonerod' procedure/method to call
rpc_input = {
"method": "getblockheaderbyhash",
"params": {"hash": block_hash}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"result": {
"status": "OK",
"block_header": {
"difficulty": 756932534,
"height": 796743,
"nonce": 8389,
"depth": 46,
"orphan_status": false,
"hash": "d78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6",
"timestamp": 1445741816,
"major_version": 1,
"minor_version": 0,
"prev_hash": "dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd",
"reward": 8349972377827
}
},
"jsonrpc": "2.0",
"id": "0"
}
Пример 3: получить полную информацию о блоке
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18082
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# the block to get
block_hash = 'd78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6'
# bitmonerod' procedure/method to call
rpc_input = {
"method": "getblock",
"params": {"hash": block_hash}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# the response will contain binary blob. For some reason
# python's json encoder will crash trying to parse such
# response. Thus, its better to remove it from the response.
response_json_clean = json.loads(
"\n".join(filter(
lambda l: "blob" not in l, response.text.split("\n")
)))
# pretty print json output
print(json.dumps(response_json_clean, indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"jsonrpc": "2.0",
"result": {
"block_header": {
"difficulty": 756932534,
"major_version": 1,
"height": 796743,
"prev_hash": "dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd",
"depth": 166,
"reward": 8349972377827,
"minor_version": 0,
"timestamp": 1445741816,
"nonce": 8389,
"orphan_status": false,
"hash": "d78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6"
},
"json": "{\n \"major_version\": 1, \n \"minor_version\": 0, \n \"timestamp\": 1445741816, \n \"prev_id\": \"dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd\", \n \"nonce\": 8389, \n \"miner_tx\": {\n \"version\": 1, \n \"unlock_time\": 796803, \n \"vin\": [ {\n \"gen\": {\n \"height\": 796743\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 9972377827, \n \"target\": {\n \"key\": \"aecebf2757be84a2d986052607ec3114969f7c9e128a051f5e13f2304287733d\"\n }\n }, {\n \"amount\": 40000000000, \n \"target\": {\n \"key\": \"c3a6d449f3fa837edbbc6beac8bc0405c6340c4e39418164b4aa1fa2202573f2\"\n }\n }, {\n \"amount\": 300000000000, \n \"target\": {\n \"key\": \"cfce614b779ab2705fc5f94a022eb983a2960ba9da02d61f430e988128236b0a\"\n }\n }, {\n \"amount\": 8000000000000, \n \"target\": {\n \"key\": \"b445b474d19ae555e048762e12ac8c406a4a6d7b0f37993dc8dabe7a31ef65b8\"\n }\n }\n ], \n \"extra\": [ 1, 243, 56, 214, 120, 176, 255, 133, 1, 251, 134, 27, 135, 49, 198, 55, 249, 146, 222, 116, 48, 103, 249, 229, 195, 120, 162, 127, 62, 35, 57, 231, 51, 2, 8, 0, 0, 0, 0, 25, 79, 41, 47\n ], \n \"signatures\": [ ]\n }, \n \"tx_hashes\": [ \"cc283dcae267c622d685b3e5f8e72aaba807dad0bb2d4170521af57c50be8165\", \"d2873b1c1800ce04434c663893a16417e8717015e9686914166f7957c5eabd68\"\n ]\n}",
"tx_hashes": [
"cc283dcae267c622d685b3e5f8e72aaba807dad0bb2d4170521af57c50be8165",
"d2873b1c1800ce04434c663893a16417e8717015e9686914166f7957c5eabd68"
],
"status": "OK"
},
"id": "0"
}
Прмер 4: получить информацию о блокчейне
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18082
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# bitmonerod' procedure/method to call
rpc_input = {
"method": "get_info"
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Результат выполнения:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"alt_blocks_count": 0,
"difficulty": 692400878,
"height": 797031,
"tx_pool_size": 1,
"grey_peerlist_size": 3447,
"outgoing_connections_count": 12,
"tx_count": 492488,
"white_peerlist_size": 253,
"target_height": 796995,
"incoming_connections_count": 0
},
"id": "0"
}