mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
468 lines
16 KiB
Python
468 lines
16 KiB
Python
import logging
|
|
from contextlib import contextmanager
|
|
|
|
from psycopg2 import pool
|
|
from psycopg2 import sql
|
|
from psycopg2.extras import DictCursor
|
|
from squeak.core import CSqueak
|
|
|
|
from squeakserver.blockchain.util import parse_block_header
|
|
from squeakserver.core.squeak_entry import SqueakEntry
|
|
from squeakserver.core.squeak_entry_with_profile import SqueakEntryWithProfile
|
|
from squeakserver.server.squeak_profile import SqueakProfile
|
|
from squeakserver.server.squeak_server import SqueakServer
|
|
from squeakserver.server.util import get_hash
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PostgresDb:
|
|
def __init__(self, params):
|
|
logger.info("Starting connection pool with params: {}".format(params))
|
|
self.connection_pool = pool.ThreadedConnectionPool(5, 20, **params)
|
|
|
|
# Get Cursor
|
|
@contextmanager
|
|
def get_cursor(self):
|
|
con = self.connection_pool.getconn()
|
|
try:
|
|
yield con.cursor(cursor_factory=DictCursor)
|
|
con.commit()
|
|
finally:
|
|
self.connection_pool.putconn(con)
|
|
|
|
def get_version(self):
|
|
""" Connect to the PostgreSQL database server """
|
|
with self.get_cursor() as curs:
|
|
# execute a statement
|
|
logger.info("PostgreSQL database version:")
|
|
curs.execute("SELECT version()")
|
|
|
|
# display the PostgreSQL database server version
|
|
db_version = curs.fetchone()
|
|
logger.info(db_version)
|
|
|
|
def init(self):
|
|
""" Create the tables and indices in the database. """
|
|
with self.get_cursor() as curs:
|
|
# execute a statement
|
|
logger.info("Setting up database tables...")
|
|
curs.execute(open("init.sql", "r").read())
|
|
|
|
def insert_squeak(self, squeak):
|
|
""" Insert a new squeak. """
|
|
sql = """
|
|
INSERT INTO squeak(hash, n_version, hash_enc_content, hash_reply_sqk, hash_block, n_block_height, vch_script_pub_key, vch_encryption_key, enc_data_key, iv, n_time, n_nonce, enc_content, vch_script_sig, author_address, vch_decryption_key)
|
|
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING hash;"""
|
|
|
|
with self.get_cursor() as curs:
|
|
# execute the INSERT statement
|
|
curs.execute(
|
|
sql,
|
|
(
|
|
get_hash(squeak).hex(),
|
|
squeak.nVersion,
|
|
squeak.hashEncContent.hex(),
|
|
squeak.hashReplySqk.hex(),
|
|
squeak.hashBlock.hex(),
|
|
squeak.nBlockHeight,
|
|
squeak.vchScriptPubKey,
|
|
squeak.vchEncryptionKey,
|
|
squeak.encDatakey.hex(),
|
|
squeak.iv.hex(),
|
|
squeak.nTime,
|
|
squeak.nNonce,
|
|
squeak.encContent.hex(),
|
|
squeak.vchScriptSig,
|
|
str(squeak.GetAddress()),
|
|
squeak.vchDecryptionKey,
|
|
),
|
|
)
|
|
# get the generated hash back
|
|
row = curs.fetchone()
|
|
return bytes.fromhex(row["hash"])
|
|
|
|
def get_squeak_entry(self, squeak_hash):
|
|
""" Get a squeak. """
|
|
sql = """
|
|
SELECT * FROM squeak WHERE hash=%s"""
|
|
|
|
squeak_hash_str = squeak_hash.hex()
|
|
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (squeak_hash_str,))
|
|
row = curs.fetchone()
|
|
return self._parse_squeak_entry(row)
|
|
|
|
def get_squeak_entry_with_profile(self, squeak_hash):
|
|
""" Get a squeak. """
|
|
sql = """
|
|
SELECT * FROM squeak
|
|
LEFT JOIN profile
|
|
ON squeak.author_address=profile.address
|
|
WHERE squeak.hash=%s
|
|
"""
|
|
|
|
squeak_hash_str = squeak_hash.hex()
|
|
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (squeak_hash_str,))
|
|
row = curs.fetchone()
|
|
return self._parse_squeak_entry_with_profile(row)
|
|
|
|
def get_followed_squeak_entries_with_profile(self):
|
|
""" Get all followed squeaks. """
|
|
sql = """
|
|
SELECT * FROM squeak
|
|
JOIN profile
|
|
ON squeak.author_address=profile.address
|
|
WHERE squeak.block_header IS NOT NULL
|
|
AND profile.following
|
|
ORDER BY n_block_height DESC, n_time DESC;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql)
|
|
rows = curs.fetchall()
|
|
return [self._parse_squeak_entry_with_profile(row) for row in rows]
|
|
|
|
def get_squeak_entries_with_profile_for_address(
|
|
self, address, min_block, max_block
|
|
):
|
|
""" Get a squeak. """
|
|
sql = """
|
|
SELECT * FROM squeak
|
|
JOIN profile
|
|
ON squeak.author_address=profile.address
|
|
WHERE squeak.block_header IS NOT NULL
|
|
AND squeak.author_address=%s
|
|
AND n_block_height >= %s
|
|
AND n_block_height <= %s
|
|
ORDER BY n_block_height DESC, n_time DESC;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (address, min_block, max_block))
|
|
rows = curs.fetchall()
|
|
return [self._parse_squeak_entry_with_profile(row) for row in rows]
|
|
|
|
def get_thread_ancestor_squeak_entries_with_profile(self, squeak_hash_str):
|
|
""" Get all reply ancestors of squeak hash. """
|
|
sql = """
|
|
WITH RECURSIVE is_thread_ancestor(hash, depth) AS (
|
|
VALUES(%s, 1)
|
|
UNION\n
|
|
SELECT squeak.hash_reply_sqk AS hash, is_thread_ancestor.depth + 1 AS depth FROM squeak, is_thread_ancestor
|
|
WHERE squeak.hash=is_thread_ancestor.hash
|
|
)
|
|
SELECT * FROM squeak
|
|
JOIN is_thread_ancestor
|
|
ON squeak.hash=is_thread_ancestor.hash
|
|
LEFT JOIN profile
|
|
ON squeak.author_address=profile.address
|
|
WHERE squeak.block_header IS NOT NULL
|
|
ORDER BY is_thread_ancestor.depth DESC;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (squeak_hash_str,))
|
|
rows = curs.fetchall()
|
|
return [self._parse_squeak_entry_with_profile(row) for row in rows]
|
|
|
|
def lookup_squeaks(self, addresses, min_block, max_block, include_unverified=False):
|
|
""" Lookup squeaks. """
|
|
sql = """
|
|
SELECT hash FROM squeak
|
|
WHERE author_address IN %s
|
|
AND n_block_height >= %s
|
|
AND n_block_height <= %s
|
|
AND vch_decryption_key IS NOT NULL
|
|
AND ((block_header IS NOT NULL) OR %s);
|
|
"""
|
|
addresses_tuple = tuple(addresses)
|
|
|
|
if not addresses:
|
|
return []
|
|
|
|
with self.get_cursor() as curs:
|
|
# mogrify to debug.
|
|
# logger.info(curs.mogrify(sql, (addresses_tuple, min_block, max_block)))
|
|
curs.execute(
|
|
sql, (addresses_tuple, min_block, max_block, include_unverified)
|
|
)
|
|
rows = curs.fetchall()
|
|
hashes = [bytes.fromhex(row["hash"]) for row in rows]
|
|
return hashes
|
|
|
|
def lookup_squeaks_by_time(
|
|
self, addresses, interval_seconds, include_unverified=False
|
|
):
|
|
""" Lookup squeaks. """
|
|
sql = """
|
|
SELECT hash FROM squeak
|
|
WHERE author_address IN %s
|
|
AND created > now() - interval '%s seconds'
|
|
AND vch_decryption_key IS NOT NULL
|
|
AND ((block_header IS NOT NULL) OR %s);
|
|
"""
|
|
addresses_tuple = tuple(addresses)
|
|
|
|
if not addresses:
|
|
return []
|
|
|
|
with self.get_cursor() as curs:
|
|
# mogrify to debug.
|
|
# logger.info(curs.mogrify(sql, (addresses_tuple, min_block, max_block)))
|
|
curs.execute(sql, (addresses_tuple, interval_seconds, include_unverified))
|
|
rows = curs.fetchall()
|
|
hashes = [bytes.fromhex(row["hash"]) for row in rows]
|
|
return hashes
|
|
|
|
def insert_profile(self, squeak_profile):
|
|
""" Insert a new squeak profile. """
|
|
sql = """
|
|
INSERT INTO profile(profile_name, private_key, address, sharing, following, whitelisted)
|
|
VALUES(%s, %s, %s, %s, %s, %s)
|
|
RETURNING profile_id;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
# execute the INSERT statement
|
|
curs.execute(
|
|
sql,
|
|
(
|
|
squeak_profile.profile_name,
|
|
squeak_profile.private_key,
|
|
squeak_profile.address,
|
|
squeak_profile.sharing,
|
|
squeak_profile.following,
|
|
squeak_profile.whitelisted,
|
|
),
|
|
)
|
|
# get the new profile id back
|
|
row = curs.fetchone()
|
|
return row["profile_id"]
|
|
|
|
def get_signing_profiles(self):
|
|
""" Get all signing profiles. """
|
|
sql = """
|
|
SELECT * FROM profile
|
|
WHERE private_key IS NOT NULL;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql)
|
|
rows = curs.fetchall()
|
|
profiles = [self._parse_squeak_profile(row) for row in rows]
|
|
return profiles
|
|
|
|
def get_contact_profiles(self):
|
|
""" Get all contact profiles. """
|
|
sql = """
|
|
SELECT * FROM profile
|
|
WHERE private_key IS NULL;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql)
|
|
rows = curs.fetchall()
|
|
profiles = [self._parse_squeak_profile(row) for row in rows]
|
|
return profiles
|
|
|
|
def get_whitelisted_profiles(self):
|
|
""" Get all whitelisted profiles. """
|
|
sql = """
|
|
SELECT * FROM profile
|
|
WHERE whitelisted;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql)
|
|
rows = curs.fetchall()
|
|
profiles = [self._parse_squeak_profile(row) for row in rows]
|
|
return profiles
|
|
|
|
def get_profile(self, profile_id):
|
|
""" Get a profile. """
|
|
sql = """
|
|
SELECT * FROM profile WHERE profile_id=%s"""
|
|
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (profile_id,))
|
|
row = curs.fetchone()
|
|
return self._parse_squeak_profile(row)
|
|
|
|
def get_profile_by_address(self, address):
|
|
""" Get a profile by address. """
|
|
sql = """
|
|
SELECT * FROM profile
|
|
WHERE address=%s;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (address,))
|
|
row = curs.fetchone()
|
|
return self._parse_squeak_profile(row)
|
|
|
|
def set_profile_whitelisted(self, profile_id, whitelisted):
|
|
""" Set a profile is whitelisted. """
|
|
sql = """
|
|
UPDATE profile
|
|
SET whitelisted=%s
|
|
WHERE profile_id=%s;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (whitelisted, profile_id,))
|
|
|
|
def set_profile_following(self, profile_id, following):
|
|
""" Set a profile is following. """
|
|
sql = """
|
|
UPDATE profile
|
|
SET following=%s
|
|
WHERE profile_id=%s;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (following, profile_id,))
|
|
|
|
def set_profile_sharing(self, profile_id, sharing):
|
|
""" Set a profile is sharing. """
|
|
sql = """
|
|
UPDATE profile
|
|
SET sharing=%s
|
|
WHERE profile_id=%s;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (sharing, profile_id,))
|
|
|
|
def get_unverified_block_squeaks(self):
|
|
""" Get all squeaks without block header. """
|
|
sql = """
|
|
SELECT hash FROM squeak
|
|
WHERE block_header IS NULL;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql)
|
|
rows = curs.fetchall()
|
|
hashes = [bytes.fromhex(row["hash"]) for row in rows]
|
|
return hashes
|
|
|
|
def delete_squeak(self, squeak_hash):
|
|
""" Delete a squeak. """
|
|
sql = """
|
|
DELETE FROM squeak WHERE hash=%s;
|
|
"""
|
|
squeak_hash_str = squeak_hash.hex()
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (squeak_hash_str,))
|
|
|
|
def mark_squeak_block_valid(self, squeak_hash, block_header):
|
|
""" Add the block header to a squeak. """
|
|
sql = """
|
|
UPDATE squeak
|
|
SET block_header=%s
|
|
WHERE hash=%s;
|
|
"""
|
|
squeak_hash_str = squeak_hash.hex()
|
|
with self.get_cursor() as curs:
|
|
# execute the UPDATE statement
|
|
curs.execute(sql, (block_header, squeak_hash_str,))
|
|
|
|
def delete_squeak(self, squeak_hash):
|
|
""" Delete a squeak. """
|
|
sql = """
|
|
DELETE FROM squeak
|
|
WHERE squeak.hash=%s
|
|
"""
|
|
squeak_hash_str = squeak_hash.hex()
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (squeak_hash_str,))
|
|
|
|
def insert_server(self, squeak_server):
|
|
""" Insert a new squeak server. """
|
|
sql = """
|
|
INSERT INTO server(server_name, server_host, server_port, sharing, following)
|
|
VALUES(%s, %s, %s, %s, %s)
|
|
RETURNING server_id;
|
|
"""
|
|
with self.get_cursor() as curs:
|
|
# execute the INSERT statement
|
|
curs.execute(
|
|
sql,
|
|
(
|
|
squeak_server.server_name,
|
|
squeak_server.host,
|
|
squeak_server.port,
|
|
squeak_server.sharing,
|
|
squeak_server.following,
|
|
),
|
|
)
|
|
# get the new server id back
|
|
row = curs.fetchone()
|
|
return row["server_id"]
|
|
|
|
def get_server(self, server_id):
|
|
""" Get a server. """
|
|
sql = """
|
|
SELECT * FROM server WHERE server_id=%s"""
|
|
|
|
with self.get_cursor() as curs:
|
|
curs.execute(sql, (server_id,))
|
|
row = curs.fetchone()
|
|
return self._parse_squeak_server(row)
|
|
|
|
def _parse_squeak_entry(self, row):
|
|
vch_decryption_key_column = row["vch_decryption_key"]
|
|
vch_decryption_key = (
|
|
bytes(vch_decryption_key_column) if vch_decryption_key_column else None
|
|
)
|
|
squeak = CSqueak(
|
|
nVersion=row["n_version"],
|
|
hashEncContent=bytes.fromhex(row["hash_enc_content"]),
|
|
hashReplySqk=bytes.fromhex(row["hash_reply_sqk"]),
|
|
hashBlock=bytes.fromhex(row["hash_block"]),
|
|
nBlockHeight=row["n_block_height"],
|
|
vchScriptPubKey=bytes(row["vch_script_pub_key"]),
|
|
vchEncryptionKey=bytes(row["vch_encryption_key"]),
|
|
encDatakey=bytes.fromhex(row["enc_data_key"]),
|
|
iv=bytes.fromhex((row["iv"])),
|
|
nTime=row["n_time"],
|
|
nNonce=row["n_nonce"],
|
|
encContent=bytes.fromhex((row["enc_content"])),
|
|
vchScriptSig=bytes(row["vch_script_sig"]),
|
|
vchDecryptionKey=vch_decryption_key,
|
|
)
|
|
block_header_column = row["block_header"]
|
|
block_header_bytes = bytes(block_header_column) if block_header_column else None
|
|
block_header = (
|
|
parse_block_header(block_header_bytes) if block_header_bytes else None
|
|
)
|
|
return SqueakEntry(squeak=squeak, block_header=block_header)
|
|
|
|
def _parse_squeak_profile(self, row):
|
|
if row is None:
|
|
return None
|
|
private_key_column = row["private_key"]
|
|
private_key = bytes(private_key_column) if private_key_column else None
|
|
return SqueakProfile(
|
|
profile_id=row["profile_id"],
|
|
profile_name=row["profile_name"],
|
|
private_key=private_key,
|
|
address=row["address"],
|
|
sharing=row["sharing"],
|
|
following=row["following"],
|
|
whitelisted=row["whitelisted"],
|
|
)
|
|
|
|
def _parse_squeak_entry_with_profile(self, row):
|
|
if row is None:
|
|
return None
|
|
squeak_entry = self._parse_squeak_entry(row)
|
|
squeak_profile = self._parse_squeak_profile(row)
|
|
return SqueakEntryWithProfile(
|
|
squeak_entry=squeak_entry, squeak_profile=squeak_profile,
|
|
)
|
|
|
|
def _parse_squeak_server(self, row):
|
|
if row is None:
|
|
return None
|
|
return SqueakServer(
|
|
server_id=row["server_id"],
|
|
server_name=row["server_name"],
|
|
host=row["server_host"],
|
|
port=row["server_port"],
|
|
sharing=row["sharing"],
|
|
following=row["following"],
|
|
)
|