mirror of
https://github.com/planetmint/planetmint.git
synced 2025-11-25 06:55:45 +00:00
restored back query.py (mongodb), and query.py(tarantool) move to folder tarantool
This commit is contained in:
parent
29069f380c
commit
1c444cda79
@ -1,5 +1,5 @@
|
|||||||
# Copyright © 2020 Interplanetary Database Association e.V.,
|
# Copyright © 2020 Interplanetary Database Association e.V.,
|
||||||
# Planetmint and IPDB software contributors.
|
# BigchainDB and IPDB software contributors.
|
||||||
# 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
|
||||||
|
|
||||||
@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
from pymongo import DESCENDING
|
from pymongo import DESCENDING
|
||||||
|
|
||||||
from planetmint import backend
|
from bigchaindb import backend
|
||||||
from planetmint.backend.exceptions import DuplicateKeyError
|
from bigchaindb.backend.exceptions import DuplicateKeyError
|
||||||
from planetmint.backend.utils import module_dispatch_registrar
|
from bigchaindb.backend.utils import module_dispatch_registrar
|
||||||
from planetmint.backend.localmongodb.connection import LocalMongoDBConnection
|
from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection
|
||||||
from planetmint.common.transaction import Transaction
|
from bigchaindb.common.transaction import Transaction
|
||||||
|
|
||||||
register_query = module_dispatch_registrar(backend.query)
|
register_query = module_dispatch_registrar(backend.query)
|
||||||
|
|
||||||
@ -41,10 +41,10 @@ def get_transactions(conn, transaction_ids):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_metadatas(metadata, connection):
|
def store_metadatas(conn, metadata):
|
||||||
space = connection.space("meta_data")
|
return conn.run(
|
||||||
for meta in metadata:
|
conn.collection('metadata')
|
||||||
space.insert((meta["id"], meta))
|
.insert_many(metadata, ordered=False))
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -56,122 +56,89 @@ def get_metadata(conn, transaction_ids):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_asset(asset, connection):
|
def store_asset(conn, asset):
|
||||||
space = connection.space("assets")
|
try:
|
||||||
unique = token_hex(8)
|
return conn.run(
|
||||||
space.insert((asset["id"], unique, asset["data"]))
|
conn.collection('assets')
|
||||||
|
.insert_one(asset))
|
||||||
|
except DuplicateKeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_assets(assets, connection):
|
def store_assets(conn, assets):
|
||||||
space = connection.space("assets")
|
return conn.run(
|
||||||
for asset in assets:
|
conn.collection('assets')
|
||||||
unique = token_hex(8)
|
.insert_many(assets, ordered=False))
|
||||||
space.insert((asset["id"], unique, asset["data"]))
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_asset(asset_id: str, space):
|
def get_asset(conn, asset_id):
|
||||||
_data = space.select(asset_id, index="assetid_search")
|
try:
|
||||||
_data = _data.data[0]
|
return conn.run(
|
||||||
return {"data": _data[1]}
|
conn.collection('assets')
|
||||||
|
.find_one({'id': asset_id}, {'_id': 0, 'id': 0}))
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_assets(assets_ids: list, space):
|
def get_assets(conn, asset_ids):
|
||||||
_returned_data = []
|
return conn.run(
|
||||||
for _id in assets_ids:
|
conn.collection('assets')
|
||||||
asset = space.select(_id, index="assetid_search")
|
.find({'id': {'$in': asset_ids}},
|
||||||
asset = asset.data[0]
|
projection={'_id': False}))
|
||||||
_returned_data.append({"id": asset[0], "data": asset[1]})
|
|
||||||
return _returned_data
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_spent(fullfil_transaction_id: str, fullfil_output_index: str, connection):
|
def get_spent(conn, transaction_id, output):
|
||||||
_transaction_object = formats.transactions.copy()
|
query = {'inputs':
|
||||||
_transaction_object["inputs"] = []
|
{'$elemMatch':
|
||||||
_transaction_object["outputs"] = []
|
{'$and': [{'fulfills.transaction_id': transaction_id},
|
||||||
space = connection.space("inputs")
|
{'fulfills.output_index': output}]}}}
|
||||||
_inputs = space.select([fullfil_transaction_id, fullfil_output_index], index="spent_search")
|
|
||||||
_inputs = _inputs.data
|
return conn.run(
|
||||||
_transaction_object["id"] = _inputs[0][0]
|
conn.collection('transactions')
|
||||||
_transaction_object["inputs"] = [
|
.find(query, {'_id': 0}))
|
||||||
{
|
|
||||||
"owners_before": _in[2],
|
|
||||||
"fulfills": {"transaction_id": _in[3], "output_index": _in[4]},
|
|
||||||
"fulfillment": _in[1]
|
|
||||||
} for _in in _inputs
|
|
||||||
]
|
|
||||||
space = connection.space("outputs")
|
|
||||||
_outputs = space.select(_transaction_object["id"], index="id_search")
|
|
||||||
_outputs = _outputs.data
|
|
||||||
_transaction_object["outputs"] = [
|
|
||||||
{
|
|
||||||
"public_keys": _out[5],
|
|
||||||
"amount": _out[1],
|
|
||||||
"condition": {"details": {"type": _out[3], "public_key": _out[4]}, "uri": _out[2]}
|
|
||||||
} for _out in _outputs
|
|
||||||
]
|
|
||||||
return _transaction_object
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def latest_block(connection): # TODO Here is used DESCENDING OPERATOR
|
def get_latest_block(conn):
|
||||||
space = connection.space("blocks")
|
return conn.run(
|
||||||
_all_blocks = space.select()
|
conn.collection('blocks')
|
||||||
_all_blocks = _all_blocks.data
|
.find_one(projection={'_id': False},
|
||||||
_block = sorted(_all_blocks, key=itemgetter(1))[0]
|
sort=[('height', DESCENDING)]))
|
||||||
space = connection.space("blocks_tx")
|
|
||||||
_txids = space.select(_block[2], index="block_search")
|
|
||||||
_txids = _txids.data
|
|
||||||
return {"app_hash": _block[1], "height": _block[1], "transactions": [tx[0] for tx in _txids]}
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_block(block, connection):
|
def store_block(conn, block):
|
||||||
space = connection.space("blocks")
|
try:
|
||||||
block_unique_id = token_hex(8)
|
return conn.run(
|
||||||
space.insert((block["app_hash"],
|
conn.collection('blocks')
|
||||||
block["height"],
|
.insert_one(block))
|
||||||
block_unique_id))
|
except DuplicateKeyError:
|
||||||
space = connection.space("blocks_tx")
|
pass
|
||||||
for txid in block["transactions"]:
|
|
||||||
space.insert((txid, block_unique_id))
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_txids_filtered(connection, asset_id, operation=None, last_tx=None): # TODO here is used 'OR' operator
|
def get_txids_filtered(conn, asset_id, operation=None, last_tx=None):
|
||||||
_transaction_object = formats.transactions.copy()
|
|
||||||
_transaction_object["inputs"] = []
|
|
||||||
_transaction_object["outputs"] = []
|
|
||||||
|
|
||||||
actions = {
|
match = {
|
||||||
"CREATE": {"sets": ["CREATE", asset_id], "index": "transaction_search"},
|
Transaction.CREATE: {'operation': 'CREATE', 'id': asset_id},
|
||||||
# 1 - operation, 2 - id (only in transactions) +
|
Transaction.TRANSFER: {'operation': 'TRANSFER', 'asset.id': asset_id},
|
||||||
"TRANSFER": {"sets": ["TRANSFER", asset_id], "index": "asset_search"},
|
None: {'$or': [{'asset.id': asset_id}, {'id': asset_id}]},
|
||||||
# 1 - operation, 2 - asset.id (linked mode) + OPERATOR OR
|
|
||||||
None: {"sets": [asset_id, asset_id], "index": "both_search"}
|
|
||||||
}[operation]
|
}[operation]
|
||||||
space = connection.space("transactions")
|
|
||||||
if actions["sets"][0] == "CREATE":
|
cursor = conn.run(conn.collection('transactions').find(match))
|
||||||
_transactions = space.select([operation, asset_id], index=actions["index"])
|
|
||||||
_transactions = _transactions.data
|
|
||||||
elif actions["sets"][0] == "TRANSFER":
|
|
||||||
_transactions = space.select([operation, asset_id], index=actions["index"])
|
|
||||||
_transactions = _transactions.data
|
|
||||||
else:
|
|
||||||
_transactions = space.select([asset_id, asset_id], index=actions["index"])
|
|
||||||
_transactions = _transactions.data
|
|
||||||
|
|
||||||
if last_tx:
|
if last_tx:
|
||||||
_transactions = [_transactions[0]]
|
cursor = cursor.sort([('$natural', DESCENDING)]).limit(1)
|
||||||
|
|
||||||
return tuple([elem[0] for elem in _transactions])
|
return (elem['id'] for elem in cursor)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def text_search(conn, search, *, language='english', case_sensitive=False, # TODO review text search in tarantool (maybe, remove)
|
def text_search(conn, search, *, language='english', case_sensitive=False,
|
||||||
diacritic_sensitive=False, text_score=False, limit=0, table='assets'):
|
diacritic_sensitive=False, text_score=False, limit=0, table='assets'):
|
||||||
cursor = conn.run(
|
cursor = conn.run(
|
||||||
conn.collection(table)
|
conn.collection(table)
|
||||||
@ -196,100 +163,46 @@ def _remove_text_score(asset):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_owned_ids(connection, owner): # TODO implement 'group_transactions_by_id'
|
def get_owned_ids(conn, owner):
|
||||||
space = connection.space("keys")
|
cursor = conn.run(
|
||||||
_keys = space.select(owner, index="keys_search")
|
conn.collection('transactions').aggregate([
|
||||||
|
{'$match': {'outputs.public_keys': owner}},
|
||||||
_outputid = _keys[0][1]
|
{'$project': {'_id': False}}
|
||||||
_transactionid = _keys[0][0]
|
]))
|
||||||
|
return cursor
|
||||||
_transaction_object = formats.transactions.copy()
|
|
||||||
_transaction_object["inputs"] = []
|
|
||||||
_transaction_object["outputs"] = []
|
|
||||||
|
|
||||||
_transactions = []
|
|
||||||
|
|
||||||
_all_keys = space.select(_outputid, index="id_search")
|
|
||||||
_all_keys = _all_keys.data
|
|
||||||
|
|
||||||
space = connection.space("transactions")
|
|
||||||
_all_transactions = space.select(_transactionid, index="id_search")
|
|
||||||
_all_transactions = _all_transactions.data
|
|
||||||
|
|
||||||
space = connection.space("inputs")
|
|
||||||
_all_inputs = space.select(_transactionid, index="id_search")
|
|
||||||
_all_inputs = _all_inputs.data
|
|
||||||
|
|
||||||
space = connection.space("outputs")
|
|
||||||
_all_outputs = space.select(_transactionid, index="id_search")
|
|
||||||
_all_outputs = _all_outputs.data
|
|
||||||
|
|
||||||
for tsobject in _all_transactions:
|
|
||||||
local_ts = _transaction_object.copy()
|
|
||||||
local_ts["id"] = tsobject[0]
|
|
||||||
local_ts["operation"] = tsobject[1]
|
|
||||||
local_ts["version"] = tsobject[2]
|
|
||||||
|
|
||||||
for _in in _all_inputs:
|
|
||||||
if _in[0] == tsobject[0]:
|
|
||||||
local_ts["inputs"].append(
|
|
||||||
{
|
|
||||||
"owners_before": _in[2],
|
|
||||||
"fulffils": {"transaction_id": _in[3], "output_index": _in[4]},
|
|
||||||
"fulffilment": _in[1]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for _out in _all_outputs:
|
|
||||||
if _out[0] == tsobject[0]:
|
|
||||||
local_ts["outputs"].append(
|
|
||||||
{
|
|
||||||
"public_keys": [_key[2] for _key in _all_keys if _out[5] == _key[1]],
|
|
||||||
"condition": {"details": {"type": _out[3], "public_key": _out[4]}, "uri": _out[2]},
|
|
||||||
"amount": _out[1]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
_transactions.append(local_ts)
|
|
||||||
|
|
||||||
return _transactions
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_spending_transactions(inputs, connection):
|
def get_spending_transactions(conn, inputs):
|
||||||
transaction_ids = [i['transaction_id'] for i in inputs]
|
transaction_ids = [i['transaction_id'] for i in inputs]
|
||||||
output_indexes = [i['output_index'] for i in inputs]
|
output_indexes = [i['output_index'] for i in inputs]
|
||||||
|
query = {'inputs':
|
||||||
|
{'$elemMatch':
|
||||||
|
{'$and':
|
||||||
|
[
|
||||||
|
{'fulfills.transaction_id': {'$in': transaction_ids}},
|
||||||
|
{'fulfills.output_index': {'$in': output_indexes}}
|
||||||
|
]}}}
|
||||||
|
|
||||||
_transactions = []
|
cursor = conn.run(
|
||||||
|
conn.collection('transactions').find(query, {'_id': False}))
|
||||||
for i in range(0, len(transaction_ids)):
|
return cursor
|
||||||
ts_id = transaction_ids[i]
|
|
||||||
ot_id = output_indexes[i]
|
|
||||||
|
|
||||||
_trans_object = get_spent(fullfil_transaction_id=ts_id, fullfil_output_index=ot_id, connection=connection)
|
|
||||||
_transactions.append(_trans_object)
|
|
||||||
|
|
||||||
return _transactions
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_block(block_id, connection):
|
def get_block(conn, block_id):
|
||||||
space = connection.space("blocks")
|
return conn.run(
|
||||||
_block = space.select(block_id, index="block_search", limit=1)
|
conn.collection('blocks')
|
||||||
_block = _block.data[0]
|
.find_one({'height': block_id},
|
||||||
_txblock = space.select(_block[2], index="block_search")
|
projection={'_id': False}))
|
||||||
_txblock = _txblock.data
|
|
||||||
return {"app_hash": _block[0], "height": _block[1], "transactions": [_tx[0] for _tx in _txblock]}
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_block_with_transaction(txid, connection):
|
def get_block_with_transaction(conn, txid):
|
||||||
space = connection.space("blocks_tx")
|
return conn.run(
|
||||||
_all_blocks_tx = space.select(txid, index="id_search")
|
conn.collection('blocks')
|
||||||
_all_blocks_tx = _all_blocks_tx.data
|
.find({'transactions': txid},
|
||||||
space = connection.space("blocks")
|
projection={'_id': False, 'height': True}))
|
||||||
|
|
||||||
_block = space.select(_all_blocks_tx[0][1], index="block_id_search")
|
|
||||||
_block = _block.data[0]
|
|
||||||
return {"app_hash": _block[0], "height": _block[1], "transactions": [_tx[0] for _tx in _all_blocks_tx]}
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -338,15 +251,11 @@ def get_unspent_outputs(conn, *, query=None):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_pre_commit_state(state, connection):
|
def store_pre_commit_state(conn, state):
|
||||||
space = connection.space("pre_commits")
|
return conn.run(
|
||||||
_precommit = space.select(state["height"], index="height_search", limit=1)
|
conn.collection('pre_commit')
|
||||||
unique_id = token_hex(8) if (len(_precommit.data) == 0) else _precommit.data[0][0]
|
.replace_one({}, state, upsert=True)
|
||||||
space.upsert((unique_id, state["height"], state["transactions"]),
|
)
|
||||||
op_list=[('=', 0, unique_id),
|
|
||||||
('=', 1, state["height"]),
|
|
||||||
('=', 2, state["transactions"])],
|
|
||||||
limit=1)
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -355,15 +264,15 @@ def get_pre_commit_state(conn):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_validator_set(validators_update, connection):
|
def store_validator_set(conn, validators_update):
|
||||||
space = connection.space("validators")
|
height = validators_update['height']
|
||||||
_validator = space.select(validators_update["height"], index="height_search", limit=1)
|
return conn.run(
|
||||||
unique_id = token_hex(8) if (len(_validator.data) == 0) else _validator.data[0][0]
|
conn.collection('validators').replace_one(
|
||||||
space.upsert((unique_id, validators_update["height"], validators_update["validators"]),
|
{'height': height},
|
||||||
op_list=[('=', 0, unique_id),
|
validators_update,
|
||||||
('=', 1, validators_update["height"]),
|
upsert=True
|
||||||
('=', 2, validators_update["validators"])],
|
)
|
||||||
limit=1)
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -374,22 +283,24 @@ def delete_validator_set(conn, height):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_election(election_id, height, is_concluded, connection):
|
def store_election(conn, election_id, height, is_concluded):
|
||||||
space = connection.space("elections")
|
return conn.run(
|
||||||
space.upsert((election_id, height, is_concluded),
|
conn.collection('elections').replace_one(
|
||||||
op_list=[('=', 0, election_id),
|
{'election_id': election_id,
|
||||||
('=', 1, height),
|
'height': height},
|
||||||
('=', 2, is_concluded)],
|
{'election_id': election_id,
|
||||||
limit=1)
|
'height': height,
|
||||||
|
'is_concluded': is_concluded},
|
||||||
|
upsert=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_elections(elections, connection):
|
def store_elections(conn, elections):
|
||||||
space = connection.space("elections")
|
return conn.run(
|
||||||
for election in elections:
|
conn.collection('elections').insert_many(elections)
|
||||||
_election = space.insert((election["election_id"],
|
)
|
||||||
election["height"],
|
|
||||||
election["is_concluded"]))
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -400,46 +311,55 @@ def delete_elections(conn, height):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_validator_set(connection, height=None):
|
def get_validator_set(conn, height=None):
|
||||||
space = connection.space("validators")
|
query = {}
|
||||||
_validators = space.select()
|
|
||||||
_validators = _validators.data
|
|
||||||
if height is not None:
|
if height is not None:
|
||||||
_validators = [validator for validator in _validators if validator[1] <= height]
|
query = {'height': {'$lte': height}}
|
||||||
return next(iter(sorted(_validators, key=itemgetter(1))), None)
|
|
||||||
|
|
||||||
return next(iter(sorted(_validators, key=itemgetter(1))), None)
|
cursor = conn.run(
|
||||||
|
conn.collection('validators')
|
||||||
|
.find(query, projection={'_id': False})
|
||||||
|
.sort([('height', DESCENDING)])
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return next(cursor, None)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_election(election_id, connection):
|
def get_election(conn, election_id):
|
||||||
space = connection.space("elections")
|
query = {'election_id': election_id}
|
||||||
_elections = space.select(election_id, index="id_search")
|
|
||||||
_elections = _elections.data
|
return conn.run(
|
||||||
_election = sorted(_elections, key=itemgetter(0))[0]
|
conn.collection('elections')
|
||||||
return {"election_id": _election[0], "height": _election[1], "is_concluded": _election[2]}
|
.find_one(query, projection={'_id': False},
|
||||||
|
sort=[('height', DESCENDING)])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_asset_tokens_for_public_key(connection, asset_id, public_key):
|
def get_asset_tokens_for_public_key(conn, asset_id, public_key):
|
||||||
space = connection.space("keys")
|
query = {'outputs.public_keys': [public_key],
|
||||||
_keys = space.select([public_key], index="keys_search")
|
'asset.id': asset_id}
|
||||||
space = connection.space("transactions")
|
|
||||||
_transactions = space.select([asset_id], index="only_asset_search")
|
cursor = conn.run(
|
||||||
_transactions = _transactions.data
|
conn.collection('transactions').aggregate([
|
||||||
_keys = _keys.data
|
{'$match': query},
|
||||||
_grouped_transactions = group_transaction_by_ids(connection=connection, txids=[_tx[0] for _tx in _transactions])
|
{'$project': {'_id': False}}
|
||||||
return _grouped_transactions
|
]))
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def store_abci_chain(height, chain_id, connection, is_synced=True):
|
def store_abci_chain(conn, height, chain_id, is_synced=True):
|
||||||
space = connection.space("abci_chains")
|
return conn.run(
|
||||||
space.upsert((height, chain_id, is_synced),
|
conn.collection('abci_chains').replace_one(
|
||||||
op_list=[('=', 0, height),
|
{'height': height},
|
||||||
('=', 1, chain_id),
|
{'height': height, 'chain_id': chain_id,
|
||||||
('=', 2, is_synced)],
|
'is_synced': is_synced},
|
||||||
limit=1)
|
upsert=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
@ -450,8 +370,8 @@ def delete_abci_chain(conn, height):
|
|||||||
|
|
||||||
|
|
||||||
@register_query(LocalMongoDBConnection)
|
@register_query(LocalMongoDBConnection)
|
||||||
def get_latest_abci_chain(connection):
|
def get_latest_abci_chain(conn):
|
||||||
space = connection.space("abci_chains")
|
return conn.run(
|
||||||
_all_chains = space.select()
|
conn.collection('abci_chains')
|
||||||
_chain = sorted(_all_chains.data, key=itemgetter(0))[0]
|
.find_one(projection={'_id': False}, sort=[('height', DESCENDING)])
|
||||||
return {"height": _chain[0], "is_synced": _chain[1], "chain_id": _chain[2]}
|
)
|
||||||
|
|||||||
457
planetmint/backend/tarantool/query.py
Normal file
457
planetmint/backend/tarantool/query.py
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
"""Query implementation for MongoDB"""
|
||||||
|
|
||||||
|
from pymongo import DESCENDING
|
||||||
|
|
||||||
|
from planetmint import backend
|
||||||
|
from planetmint.backend.exceptions import DuplicateKeyError
|
||||||
|
from planetmint.backend.utils import module_dispatch_registrar
|
||||||
|
from planetmint.backend.localmongodb.connection import LocalMongoDBConnection
|
||||||
|
from planetmint.common.transaction import Transaction
|
||||||
|
|
||||||
|
register_query = module_dispatch_registrar(backend.query)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_transactions(conn, signed_transactions):
|
||||||
|
return conn.run(conn.collection('transactions')
|
||||||
|
.insert_many(signed_transactions))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_transaction(conn, transaction_id):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('transactions')
|
||||||
|
.find_one({'id': transaction_id}, {'_id': 0}))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_transactions(conn, transaction_ids):
|
||||||
|
try:
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('transactions')
|
||||||
|
.find({'id': {'$in': transaction_ids}},
|
||||||
|
projection={'_id': False}))
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_metadatas(metadata, connection):
|
||||||
|
space = connection.space("meta_data")
|
||||||
|
for meta in metadata:
|
||||||
|
space.insert((meta["id"], meta))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_metadata(conn, transaction_ids):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('metadata')
|
||||||
|
.find({'id': {'$in': transaction_ids}},
|
||||||
|
projection={'_id': False}))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_asset(asset, connection):
|
||||||
|
space = connection.space("assets")
|
||||||
|
unique = token_hex(8)
|
||||||
|
space.insert((asset["id"], unique, asset["data"]))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_assets(assets, connection):
|
||||||
|
space = connection.space("assets")
|
||||||
|
for asset in assets:
|
||||||
|
unique = token_hex(8)
|
||||||
|
space.insert((asset["id"], unique, asset["data"]))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_asset(asset_id: str, space):
|
||||||
|
_data = space.select(asset_id, index="assetid_search")
|
||||||
|
_data = _data.data[0]
|
||||||
|
return {"data": _data[1]}
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_assets(assets_ids: list, space):
|
||||||
|
_returned_data = []
|
||||||
|
for _id in assets_ids:
|
||||||
|
asset = space.select(_id, index="assetid_search")
|
||||||
|
asset = asset.data[0]
|
||||||
|
_returned_data.append({"id": asset[0], "data": asset[1]})
|
||||||
|
return _returned_data
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_spent(fullfil_transaction_id: str, fullfil_output_index: str, connection):
|
||||||
|
_transaction_object = formats.transactions.copy()
|
||||||
|
_transaction_object["inputs"] = []
|
||||||
|
_transaction_object["outputs"] = []
|
||||||
|
space = connection.space("inputs")
|
||||||
|
_inputs = space.select([fullfil_transaction_id, fullfil_output_index], index="spent_search")
|
||||||
|
_inputs = _inputs.data
|
||||||
|
_transaction_object["id"] = _inputs[0][0]
|
||||||
|
_transaction_object["inputs"] = [
|
||||||
|
{
|
||||||
|
"owners_before": _in[2],
|
||||||
|
"fulfills": {"transaction_id": _in[3], "output_index": _in[4]},
|
||||||
|
"fulfillment": _in[1]
|
||||||
|
} for _in in _inputs
|
||||||
|
]
|
||||||
|
space = connection.space("outputs")
|
||||||
|
_outputs = space.select(_transaction_object["id"], index="id_search")
|
||||||
|
_outputs = _outputs.data
|
||||||
|
_transaction_object["outputs"] = [
|
||||||
|
{
|
||||||
|
"public_keys": _out[5],
|
||||||
|
"amount": _out[1],
|
||||||
|
"condition": {"details": {"type": _out[3], "public_key": _out[4]}, "uri": _out[2]}
|
||||||
|
} for _out in _outputs
|
||||||
|
]
|
||||||
|
return _transaction_object
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def latest_block(connection): # TODO Here is used DESCENDING OPERATOR
|
||||||
|
space = connection.space("blocks")
|
||||||
|
_all_blocks = space.select()
|
||||||
|
_all_blocks = _all_blocks.data
|
||||||
|
_block = sorted(_all_blocks, key=itemgetter(1))[0]
|
||||||
|
space = connection.space("blocks_tx")
|
||||||
|
_txids = space.select(_block[2], index="block_search")
|
||||||
|
_txids = _txids.data
|
||||||
|
return {"app_hash": _block[1], "height": _block[1], "transactions": [tx[0] for tx in _txids]}
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_block(block, connection):
|
||||||
|
space = connection.space("blocks")
|
||||||
|
block_unique_id = token_hex(8)
|
||||||
|
space.insert((block["app_hash"],
|
||||||
|
block["height"],
|
||||||
|
block_unique_id))
|
||||||
|
space = connection.space("blocks_tx")
|
||||||
|
for txid in block["transactions"]:
|
||||||
|
space.insert((txid, block_unique_id))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_txids_filtered(connection, asset_id, operation=None, last_tx=None): # TODO here is used 'OR' operator
|
||||||
|
_transaction_object = formats.transactions.copy()
|
||||||
|
_transaction_object["inputs"] = []
|
||||||
|
_transaction_object["outputs"] = []
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
"CREATE": {"sets": ["CREATE", asset_id], "index": "transaction_search"},
|
||||||
|
# 1 - operation, 2 - id (only in transactions) +
|
||||||
|
"TRANSFER": {"sets": ["TRANSFER", asset_id], "index": "asset_search"},
|
||||||
|
# 1 - operation, 2 - asset.id (linked mode) + OPERATOR OR
|
||||||
|
None: {"sets": [asset_id, asset_id], "index": "both_search"}
|
||||||
|
}[operation]
|
||||||
|
space = connection.space("transactions")
|
||||||
|
if actions["sets"][0] == "CREATE":
|
||||||
|
_transactions = space.select([operation, asset_id], index=actions["index"])
|
||||||
|
_transactions = _transactions.data
|
||||||
|
elif actions["sets"][0] == "TRANSFER":
|
||||||
|
_transactions = space.select([operation, asset_id], index=actions["index"])
|
||||||
|
_transactions = _transactions.data
|
||||||
|
else:
|
||||||
|
_transactions = space.select([asset_id, asset_id], index=actions["index"])
|
||||||
|
_transactions = _transactions.data
|
||||||
|
|
||||||
|
if last_tx:
|
||||||
|
_transactions = [_transactions[0]]
|
||||||
|
|
||||||
|
return tuple([elem[0] for elem in _transactions])
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def text_search(conn, search, *, language='english', case_sensitive=False, # TODO review text search in tarantool (maybe, remove)
|
||||||
|
diacritic_sensitive=False, text_score=False, limit=0, table='assets'):
|
||||||
|
cursor = conn.run(
|
||||||
|
conn.collection(table)
|
||||||
|
.find({'$text': {
|
||||||
|
'$search': search,
|
||||||
|
'$language': language,
|
||||||
|
'$caseSensitive': case_sensitive,
|
||||||
|
'$diacriticSensitive': diacritic_sensitive}},
|
||||||
|
{'score': {'$meta': 'textScore'}, '_id': False})
|
||||||
|
.sort([('score', {'$meta': 'textScore'})])
|
||||||
|
.limit(limit))
|
||||||
|
|
||||||
|
if text_score:
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
return (_remove_text_score(obj) for obj in cursor)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_text_score(asset):
|
||||||
|
asset.pop('score', None)
|
||||||
|
return asset
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_owned_ids(connection, owner): # TODO implement 'group_transactions_by_id'
|
||||||
|
space = connection.space("keys")
|
||||||
|
_keys = space.select(owner, index="keys_search")
|
||||||
|
|
||||||
|
_outputid = _keys[0][1]
|
||||||
|
_transactionid = _keys[0][0]
|
||||||
|
|
||||||
|
_transaction_object = formats.transactions.copy()
|
||||||
|
_transaction_object["inputs"] = []
|
||||||
|
_transaction_object["outputs"] = []
|
||||||
|
|
||||||
|
_transactions = []
|
||||||
|
|
||||||
|
_all_keys = space.select(_outputid, index="id_search")
|
||||||
|
_all_keys = _all_keys.data
|
||||||
|
|
||||||
|
space = connection.space("transactions")
|
||||||
|
_all_transactions = space.select(_transactionid, index="id_search")
|
||||||
|
_all_transactions = _all_transactions.data
|
||||||
|
|
||||||
|
space = connection.space("inputs")
|
||||||
|
_all_inputs = space.select(_transactionid, index="id_search")
|
||||||
|
_all_inputs = _all_inputs.data
|
||||||
|
|
||||||
|
space = connection.space("outputs")
|
||||||
|
_all_outputs = space.select(_transactionid, index="id_search")
|
||||||
|
_all_outputs = _all_outputs.data
|
||||||
|
|
||||||
|
for tsobject in _all_transactions:
|
||||||
|
local_ts = _transaction_object.copy()
|
||||||
|
local_ts["id"] = tsobject[0]
|
||||||
|
local_ts["operation"] = tsobject[1]
|
||||||
|
local_ts["version"] = tsobject[2]
|
||||||
|
|
||||||
|
for _in in _all_inputs:
|
||||||
|
if _in[0] == tsobject[0]:
|
||||||
|
local_ts["inputs"].append(
|
||||||
|
{
|
||||||
|
"owners_before": _in[2],
|
||||||
|
"fulffils": {"transaction_id": _in[3], "output_index": _in[4]},
|
||||||
|
"fulffilment": _in[1]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for _out in _all_outputs:
|
||||||
|
if _out[0] == tsobject[0]:
|
||||||
|
local_ts["outputs"].append(
|
||||||
|
{
|
||||||
|
"public_keys": [_key[2] for _key in _all_keys if _out[5] == _key[1]],
|
||||||
|
"condition": {"details": {"type": _out[3], "public_key": _out[4]}, "uri": _out[2]},
|
||||||
|
"amount": _out[1]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_transactions.append(local_ts)
|
||||||
|
|
||||||
|
return _transactions
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_spending_transactions(inputs, connection):
|
||||||
|
transaction_ids = [i['transaction_id'] for i in inputs]
|
||||||
|
output_indexes = [i['output_index'] for i in inputs]
|
||||||
|
|
||||||
|
_transactions = []
|
||||||
|
|
||||||
|
for i in range(0, len(transaction_ids)):
|
||||||
|
ts_id = transaction_ids[i]
|
||||||
|
ot_id = output_indexes[i]
|
||||||
|
|
||||||
|
_trans_object = get_spent(fullfil_transaction_id=ts_id, fullfil_output_index=ot_id, connection=connection)
|
||||||
|
_transactions.append(_trans_object)
|
||||||
|
|
||||||
|
return _transactions
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_block(block_id, connection):
|
||||||
|
space = connection.space("blocks")
|
||||||
|
_block = space.select(block_id, index="block_search", limit=1)
|
||||||
|
_block = _block.data[0]
|
||||||
|
_txblock = space.select(_block[2], index="block_search")
|
||||||
|
_txblock = _txblock.data
|
||||||
|
return {"app_hash": _block[0], "height": _block[1], "transactions": [_tx[0] for _tx in _txblock]}
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_block_with_transaction(txid, connection):
|
||||||
|
space = connection.space("blocks_tx")
|
||||||
|
_all_blocks_tx = space.select(txid, index="id_search")
|
||||||
|
_all_blocks_tx = _all_blocks_tx.data
|
||||||
|
space = connection.space("blocks")
|
||||||
|
|
||||||
|
_block = space.select(_all_blocks_tx[0][1], index="block_id_search")
|
||||||
|
_block = _block.data[0]
|
||||||
|
return {"app_hash": _block[0], "height": _block[1], "transactions": [_tx[0] for _tx in _all_blocks_tx]}
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def delete_transactions(conn, txn_ids):
|
||||||
|
conn.run(conn.collection('assets').delete_many({'id': {'$in': txn_ids}}))
|
||||||
|
conn.run(conn.collection('metadata').delete_many({'id': {'$in': txn_ids}}))
|
||||||
|
conn.run(conn.collection('transactions').delete_many({'id': {'$in': txn_ids}}))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_unspent_outputs(conn, *unspent_outputs):
|
||||||
|
if unspent_outputs:
|
||||||
|
try:
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('utxos').insert_many(
|
||||||
|
unspent_outputs,
|
||||||
|
ordered=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except DuplicateKeyError:
|
||||||
|
# TODO log warning at least
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def delete_unspent_outputs(conn, *unspent_outputs):
|
||||||
|
if unspent_outputs:
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('utxos').delete_many({
|
||||||
|
'$or': [{
|
||||||
|
'$and': [
|
||||||
|
{'transaction_id': unspent_output['transaction_id']},
|
||||||
|
{'output_index': unspent_output['output_index']},
|
||||||
|
],
|
||||||
|
} for unspent_output in unspent_outputs]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_unspent_outputs(conn, *, query=None):
|
||||||
|
if query is None:
|
||||||
|
query = {}
|
||||||
|
return conn.run(conn.collection('utxos').find(query,
|
||||||
|
projection={'_id': False}))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_pre_commit_state(state, connection):
|
||||||
|
space = connection.space("pre_commits")
|
||||||
|
_precommit = space.select(state["height"], index="height_search", limit=1)
|
||||||
|
unique_id = token_hex(8) if (len(_precommit.data) == 0) else _precommit.data[0][0]
|
||||||
|
space.upsert((unique_id, state["height"], state["transactions"]),
|
||||||
|
op_list=[('=', 0, unique_id),
|
||||||
|
('=', 1, state["height"]),
|
||||||
|
('=', 2, state["transactions"])],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_pre_commit_state(conn):
|
||||||
|
return conn.run(conn.collection('pre_commit').find_one())
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_validator_set(validators_update, connection):
|
||||||
|
space = connection.space("validators")
|
||||||
|
_validator = space.select(validators_update["height"], index="height_search", limit=1)
|
||||||
|
unique_id = token_hex(8) if (len(_validator.data) == 0) else _validator.data[0][0]
|
||||||
|
space.upsert((unique_id, validators_update["height"], validators_update["validators"]),
|
||||||
|
op_list=[('=', 0, unique_id),
|
||||||
|
('=', 1, validators_update["height"]),
|
||||||
|
('=', 2, validators_update["validators"])],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def delete_validator_set(conn, height):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('validators').delete_many({'height': height})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_election(election_id, height, is_concluded, connection):
|
||||||
|
space = connection.space("elections")
|
||||||
|
space.upsert((election_id, height, is_concluded),
|
||||||
|
op_list=[('=', 0, election_id),
|
||||||
|
('=', 1, height),
|
||||||
|
('=', 2, is_concluded)],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_elections(elections, connection):
|
||||||
|
space = connection.space("elections")
|
||||||
|
for election in elections:
|
||||||
|
_election = space.insert((election["election_id"],
|
||||||
|
election["height"],
|
||||||
|
election["is_concluded"]))
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def delete_elections(conn, height):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('elections').delete_many({'height': height})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_validator_set(connection, height=None):
|
||||||
|
space = connection.space("validators")
|
||||||
|
_validators = space.select()
|
||||||
|
_validators = _validators.data
|
||||||
|
if height is not None:
|
||||||
|
_validators = [validator for validator in _validators if validator[1] <= height]
|
||||||
|
return next(iter(sorted(_validators, key=itemgetter(1))), None)
|
||||||
|
|
||||||
|
return next(iter(sorted(_validators, key=itemgetter(1))), None)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_election(election_id, connection):
|
||||||
|
space = connection.space("elections")
|
||||||
|
_elections = space.select(election_id, index="id_search")
|
||||||
|
_elections = _elections.data
|
||||||
|
_election = sorted(_elections, key=itemgetter(0))[0]
|
||||||
|
return {"election_id": _election[0], "height": _election[1], "is_concluded": _election[2]}
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_asset_tokens_for_public_key(connection, asset_id, public_key):
|
||||||
|
space = connection.space("keys")
|
||||||
|
_keys = space.select([public_key], index="keys_search")
|
||||||
|
space = connection.space("transactions")
|
||||||
|
_transactions = space.select([asset_id], index="only_asset_search")
|
||||||
|
_transactions = _transactions.data
|
||||||
|
_keys = _keys.data
|
||||||
|
_grouped_transactions = group_transaction_by_ids(connection=connection, txids=[_tx[0] for _tx in _transactions])
|
||||||
|
return _grouped_transactions
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def store_abci_chain(height, chain_id, connection, is_synced=True):
|
||||||
|
space = connection.space("abci_chains")
|
||||||
|
space.upsert((height, chain_id, is_synced),
|
||||||
|
op_list=[('=', 0, height),
|
||||||
|
('=', 1, chain_id),
|
||||||
|
('=', 2, is_synced)],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def delete_abci_chain(conn, height):
|
||||||
|
return conn.run(
|
||||||
|
conn.collection('abci_chains').delete_many({'height': height})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_query(LocalMongoDBConnection)
|
||||||
|
def get_latest_abci_chain(connection):
|
||||||
|
space = connection.space("abci_chains")
|
||||||
|
_all_chains = space.select()
|
||||||
|
_chain = sorted(_all_chains.data, key=itemgetter(0))[0]
|
||||||
|
return {"height": _chain[0], "is_synced": _chain[1], "chain_id": _chain[2]}
|
||||||
Loading…
x
Reference in New Issue
Block a user