2018-10-15 04:16:51 +02:00
|
|
|
import unittest
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-11-04 06:55:50 +01:00
|
|
|
from torba.client.wallet import Wallet
|
|
|
|
from torba.client.constants import COIN
|
2018-10-03 13:08:02 +02:00
|
|
|
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
|
2018-11-04 06:55:50 +01:00
|
|
|
from torba.client.basedatabase import query, constraints_to_sql
|
2018-08-04 03:26:53 +02:00
|
|
|
|
2018-11-04 06:55:50 +01:00
|
|
|
from torba.testcase import AsyncioTestCase
|
2018-10-15 04:16:51 +02:00
|
|
|
|
2018-11-04 06:55:50 +01:00
|
|
|
from client_tests.unit.test_transaction import get_output, NULL_HASH
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-08-04 03:26:53 +02:00
|
|
|
|
2018-10-07 20:53:44 +02:00
|
|
|
class TestQueryBuilder(unittest.TestCase):
|
2018-08-04 03:26:53 +02:00
|
|
|
|
2018-10-03 17:51:42 +02:00
|
|
|
def test_dot(self):
|
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({'txo.position': 18}),
|
|
|
|
('txo.position = :txo_position', {'txo_position': 18})
|
2018-10-03 17:51:42 +02:00
|
|
|
)
|
|
|
|
|
2018-08-04 03:26:53 +02:00
|
|
|
def test_any(self):
|
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({
|
|
|
|
'ages__any': {
|
|
|
|
'txo.age__gt': 18,
|
|
|
|
'txo.age__lt': 38
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
('(txo.age > :ages__any_txo_age__gt OR txo.age < :ages__any_txo_age__lt)', {
|
2018-10-03 17:51:42 +02:00
|
|
|
'ages__any_txo_age__gt': 18,
|
|
|
|
'ages__any_txo_age__lt': 38
|
2018-10-07 20:53:44 +02:00
|
|
|
})
|
2018-08-04 03:26:53 +02:00
|
|
|
)
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 19:25:18 +02:00
|
|
|
def test_in(self):
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({'txo.age__in': [18, 38]}),
|
|
|
|
('txo.age IN (18, 38)', {})
|
2018-10-03 13:08:02 +02:00
|
|
|
)
|
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({'txo.age__in': ['abc123', 'def456']}),
|
|
|
|
("txo.age IN ('abc123', 'def456')", {})
|
2018-10-03 13:08:02 +02:00
|
|
|
)
|
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({'txo.age__in': 'SELECT age from ages_table'}),
|
|
|
|
('txo.age IN (SELECT age from ages_table)', {})
|
2018-10-03 13:08:02 +02:00
|
|
|
)
|
|
|
|
|
2018-10-15 19:25:18 +02:00
|
|
|
def test_not_in(self):
|
|
|
|
self.assertEqual(
|
|
|
|
constraints_to_sql({'txo.age__not_in': [18, 38]}),
|
|
|
|
('txo.age NOT IN (18, 38)', {})
|
|
|
|
)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual(
|
2018-10-07 20:53:44 +02:00
|
|
|
constraints_to_sql({'txo.age__not_in': 'SELECT age from ages_table'}),
|
|
|
|
('txo.age NOT IN (SELECT age from ages_table)', {})
|
2018-10-03 13:08:02 +02:00
|
|
|
)
|
|
|
|
|
2018-10-03 17:51:42 +02:00
|
|
|
def test_in_invalid(self):
|
2018-10-07 20:53:44 +02:00
|
|
|
with self.assertRaisesRegex(ValueError, 'list, set or string'):
|
|
|
|
constraints_to_sql({'ages__in': 9})
|
|
|
|
|
|
|
|
def test_query(self):
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo"),
|
|
|
|
("select * from foo", {})
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
query(
|
|
|
|
"select * from foo",
|
2018-10-15 19:25:18 +02:00
|
|
|
a__not='b', b__in='select * from blah where c=:$c',
|
|
|
|
d__any={'one__like': 'o', 'two': 2}, limit=10, order_by='b', **{'$c': 3}),
|
2018-10-07 20:53:44 +02:00
|
|
|
(
|
2018-10-15 19:25:18 +02:00
|
|
|
"select * from foo WHERE a != :a__not AND "
|
2018-10-07 20:53:44 +02:00
|
|
|
"b IN (select * from blah where c=:$c) AND "
|
2018-10-15 19:25:18 +02:00
|
|
|
"(one LIKE :d__any_one__like OR two = :d__any_two) ORDER BY b LIMIT 10",
|
|
|
|
{'a__not': 'b', 'd__any_one__like': 'o', 'd__any_two': 2, '$c': 3}
|
2018-10-07 20:53:44 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_query_order_by(self):
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo", order_by='foo'),
|
|
|
|
("select * from foo ORDER BY foo", {})
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo", order_by=['foo', 'bar']),
|
|
|
|
("select * from foo ORDER BY foo, bar", {})
|
|
|
|
)
|
2018-10-15 19:25:18 +02:00
|
|
|
with self.assertRaisesRegex(ValueError, 'order_by must be string or list'):
|
|
|
|
query("select * from foo", order_by={'foo': 'bar'})
|
2018-10-07 20:53:44 +02:00
|
|
|
|
|
|
|
def test_query_limit_offset(self):
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo", limit=10),
|
|
|
|
("select * from foo LIMIT 10", {})
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo", offset=10),
|
|
|
|
("select * from foo OFFSET 10", {})
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
query("select * from foo", limit=20, offset=10),
|
2018-10-09 22:49:37 +02:00
|
|
|
("select * from foo LIMIT 20 OFFSET 10", {})
|
2018-10-07 20:53:44 +02:00
|
|
|
)
|
2018-10-03 17:51:42 +02:00
|
|
|
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
class TestQueries(AsyncioTestCase):
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
async def asyncSetUp(self):
|
2018-10-03 13:08:02 +02:00
|
|
|
self.ledger = ledger_class({
|
|
|
|
'db': ledger_class.database_class(':memory:'),
|
|
|
|
'headers': ledger_class.headers_class(':memory:'),
|
|
|
|
})
|
2018-10-15 04:16:51 +02:00
|
|
|
await self.ledger.db.open()
|
|
|
|
|
|
|
|
async def asyncTearDown(self):
|
|
|
|
await self.ledger.db.close()
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
async def create_account(self):
|
2018-10-03 13:08:02 +02:00
|
|
|
account = self.ledger.account_class.generate(self.ledger, Wallet())
|
2018-10-15 04:16:51 +02:00
|
|
|
await account.ensure_address_gap()
|
2018-10-03 13:08:02 +02:00
|
|
|
return account
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
async def create_tx_from_nothing(self, my_account, height):
|
|
|
|
to_address = await my_account.receiving.get_or_create_usable_address()
|
2018-10-03 13:08:02 +02:00
|
|
|
to_hash = ledger_class.address_to_hash160(to_address)
|
|
|
|
tx = ledger_class.transaction_class(height=height, is_verified=True) \
|
|
|
|
.add_inputs([self.txi(self.txo(1, NULL_HASH))]) \
|
|
|
|
.add_outputs([self.txo(1, to_hash)])
|
2018-11-19 04:54:00 +01:00
|
|
|
await self.ledger.db.insert_transaction(tx)
|
|
|
|
await self.ledger.db.save_transaction_io(tx, to_address, to_hash, '')
|
2018-10-03 13:08:02 +02:00
|
|
|
return tx
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
async def create_tx_from_txo(self, txo, to_account, height):
|
2018-10-03 13:08:02 +02:00
|
|
|
from_hash = txo.script.values['pubkey_hash']
|
|
|
|
from_address = self.ledger.hash160_to_address(from_hash)
|
2018-10-15 04:16:51 +02:00
|
|
|
to_address = await to_account.receiving.get_or_create_usable_address()
|
2018-10-03 13:08:02 +02:00
|
|
|
to_hash = ledger_class.address_to_hash160(to_address)
|
|
|
|
tx = ledger_class.transaction_class(height=height, is_verified=True) \
|
|
|
|
.add_inputs([self.txi(txo)]) \
|
|
|
|
.add_outputs([self.txo(1, to_hash)])
|
2018-11-19 04:54:00 +01:00
|
|
|
await self.ledger.db.insert_transaction(tx)
|
|
|
|
await self.ledger.db.save_transaction_io(tx, from_address, from_hash, '')
|
|
|
|
await self.ledger.db.save_transaction_io(tx, to_address, to_hash, '')
|
2018-10-03 13:08:02 +02:00
|
|
|
return tx
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
async def create_tx_to_nowhere(self, txo, height):
|
2018-10-03 13:08:02 +02:00
|
|
|
from_hash = txo.script.values['pubkey_hash']
|
|
|
|
from_address = self.ledger.hash160_to_address(from_hash)
|
|
|
|
to_hash = NULL_HASH
|
|
|
|
tx = ledger_class.transaction_class(height=height, is_verified=True) \
|
|
|
|
.add_inputs([self.txi(txo)]) \
|
|
|
|
.add_outputs([self.txo(1, to_hash)])
|
2018-11-19 04:54:00 +01:00
|
|
|
await self.ledger.db.insert_transaction(tx)
|
|
|
|
await self.ledger.db.save_transaction_io(tx, from_address, from_hash, '')
|
2018-10-03 13:08:02 +02:00
|
|
|
return tx
|
|
|
|
|
|
|
|
def txo(self, amount, address):
|
|
|
|
return get_output(int(amount*COIN), address)
|
|
|
|
|
|
|
|
def txi(self, txo):
|
|
|
|
return ledger_class.transaction_class.input_class.spend(txo)
|
|
|
|
|
2018-10-15 19:25:18 +02:00
|
|
|
async def test_queries(self):
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_address_count())
|
2018-10-15 04:16:51 +02:00
|
|
|
account1 = await self.create_account()
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(26, await self.ledger.db.get_address_count())
|
2018-10-15 04:16:51 +02:00
|
|
|
account2 = await self.create_account()
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(52, await self.ledger.db.get_address_count())
|
|
|
|
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_transaction_count())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_utxo_count())
|
|
|
|
self.assertEqual([], await self.ledger.db.get_utxos())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_txo_count())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account2))
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
tx1 = await self.create_tx_from_nothing(account1, 1)
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(1, await self.ledger.db.get_transaction_count(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_transaction_count(account=account2))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_utxo_count(account=account1))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_txo_count(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_txo_count(account=account2))
|
|
|
|
self.assertEqual(10**8, await self.ledger.db.get_balance())
|
|
|
|
self.assertEqual(10**8, await self.ledger.db.get_balance(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account2))
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
tx2 = await self.create_tx_from_txo(tx1.outputs[0], account2, 2)
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(2, await self.ledger.db.get_transaction_count(account=account1))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_transaction_count(account=account2))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_utxo_count(account=account1))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_txo_count(account=account1))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_utxo_count(account=account2))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_txo_count(account=account2))
|
|
|
|
self.assertEqual(10**8, await self.ledger.db.get_balance())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account1))
|
|
|
|
self.assertEqual(10**8, await self.ledger.db.get_balance(account=account2))
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
tx3 = await self.create_tx_to_nowhere(tx2.outputs[0], 3)
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(2, await self.ledger.db.get_transaction_count(account=account1))
|
|
|
|
self.assertEqual(2, await self.ledger.db.get_transaction_count(account=account2))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_utxo_count(account=account1))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_txo_count(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_utxo_count(account=account2))
|
|
|
|
self.assertEqual(1, await self.ledger.db.get_txo_count(account=account2))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance())
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account1))
|
|
|
|
self.assertEqual(0, await self.ledger.db.get_balance(account=account2))
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
txs = await self.ledger.db.get_transactions()
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual([tx3.id, tx2.id, tx1.id], [tx.id for tx in txs])
|
|
|
|
self.assertEqual([3, 2, 1], [tx.height for tx in txs])
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
txs = await self.ledger.db.get_transactions(account=account1)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual([tx2.id, tx1.id], [tx.id for tx in txs])
|
|
|
|
self.assertEqual(txs[0].inputs[0].is_my_account, True)
|
|
|
|
self.assertEqual(txs[0].outputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(txs[1].inputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(txs[1].outputs[0].is_my_account, True)
|
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
txs = await self.ledger.db.get_transactions(account=account2)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual([tx3.id, tx2.id], [tx.id for tx in txs])
|
|
|
|
self.assertEqual(txs[0].inputs[0].is_my_account, True)
|
|
|
|
self.assertEqual(txs[0].outputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(txs[1].inputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(txs[1].outputs[0].is_my_account, True)
|
2018-10-15 19:25:18 +02:00
|
|
|
self.assertEqual(2, await self.ledger.db.get_transaction_count(account=account2))
|
2018-10-03 13:08:02 +02:00
|
|
|
|
2018-10-15 04:16:51 +02:00
|
|
|
tx = await self.ledger.db.get_transaction(txid=tx2.id)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual(tx.id, tx2.id)
|
|
|
|
self.assertEqual(tx.inputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(tx.outputs[0].is_my_account, False)
|
2018-10-15 04:16:51 +02:00
|
|
|
tx = await self.ledger.db.get_transaction(txid=tx2.id, account=account1)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual(tx.inputs[0].is_my_account, True)
|
|
|
|
self.assertEqual(tx.outputs[0].is_my_account, False)
|
2018-10-15 04:16:51 +02:00
|
|
|
tx = await self.ledger.db.get_transaction(txid=tx2.id, account=account2)
|
2018-10-03 13:08:02 +02:00
|
|
|
self.assertEqual(tx.inputs[0].is_my_account, False)
|
|
|
|
self.assertEqual(tx.outputs[0].is_my_account, True)
|
2018-11-20 00:04:07 +01:00
|
|
|
|
|
|
|
# height 0 sorted to the top with the rest in descending order
|
|
|
|
tx4 = await self.create_tx_from_nothing(account1, 0)
|
|
|
|
txos = await self.ledger.db.get_txos()
|
|
|
|
self.assertEqual([0, 2, 1], [txo.tx_ref.height for txo in txos])
|
|
|
|
self.assertEqual([tx4.id, tx2.id, tx1.id], [txo.tx_ref.id for txo in txos])
|
|
|
|
txs = await self.ledger.db.get_transactions()
|
|
|
|
self.assertEqual([0, 3, 2, 1], [tx.height for tx in txs])
|
|
|
|
self.assertEqual([tx4.id, tx3.id, tx2.id, tx1.id], [tx.id for tx in txs])
|