Thread ancestors db query (#153)

* Add replyto field to squeak display response in rpc method

* Got thread ancestors query working mostyly

* Got thread ancestors query working

* Fix docstring for ancestor query

* Add check for ancestor query to itest
This commit is contained in:
Jonathan Zernik 2020-07-27 00:55:58 -07:00 committed by GitHub
parent e5fd3cd066
commit a37296565c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 9 deletions

View file

@ -14,13 +14,13 @@ CREATE TABLE IF NOT EXISTS squeak (
n_nonce BIGINT NOT NULL,
enc_content CHAR(2272) NOT NULL, -- Encrypted content length is always 1136 bytes (2272 hex characters).
vch_script_sig bytea NOT NULL,
address VARCHAR(35) NOT NULL, -- Maximum length of a bitcoin address is 35.
author_address VARCHAR(35) NOT NULL, -- Maximum length of a bitcoin address is 35.
vch_decryption_key bytea NOT NULL,
block_header bytea
);
CREATE INDEX IF NOT EXISTS idx_squeak_address
ON squeak(address);
ON squeak(author_address);
CREATE TABLE IF NOT EXISTS profile (
profile_id SERIAL PRIMARY KEY,

View file

@ -403,6 +403,53 @@ def run():
get_profile_by_address_response.squeak_profile.profile_name == "bob"
)
# Make another squeak as a reply
reply_1_squeak_response = admin_stub.MakeSqueak(
squeak_admin_pb2.MakeSqueakRequest(
profile_id=profile_id,
content="Reply #1",
replyto=make_squeak_hash,
)
)
reply_1_squeak_hash = reply_1_squeak_response.squeak_hash
print("Get reply #1 squeak hash: " + str(reply_1_squeak_hash))
# Make a second squeak as a reply
reply_2_squeak_response = admin_stub.MakeSqueak(
squeak_admin_pb2.MakeSqueakRequest(
profile_id=profile_id,
content="Reply #2",
replyto=reply_1_squeak_hash,
)
)
reply_2_squeak_hash = reply_2_squeak_response.squeak_hash
print("Get make reply squeak response: " + str(reply_2_squeak_response))
# Get the squeak and check that the reply field is correct
get_reply_squeak_display_response = admin_stub.GetSqueakDisplay(
squeak_admin_pb2.GetSqueakDisplayRequest(
squeak_hash=reply_2_squeak_hash,
)
)
print("Get reply squeak display entry response: " + str(get_reply_squeak_display_response))
assert (
get_reply_squeak_display_response.squeak_display_entry.squeak_hash == reply_2_squeak_hash
)
assert (
get_reply_squeak_display_response.squeak_display_entry.reply_to == reply_1_squeak_hash
)
# Get the ancestors of the latest reply squeak
get_ancestors_response = admin_stub.GetAncestorSqueakDisplays(
squeak_admin_pb2.GetAncestorSqueakDisplaysRequest(
squeak_hash=reply_2_squeak_hash,
)
)
print("Get ancestor squeak display entries response: " + str(get_ancestors_response))
assert (
len(get_ancestors_response.squeak_display_entries) == 3
)
if __name__ == "__main__":
logging.basicConfig()

View file

@ -62,6 +62,10 @@ service SqueakAdmin {
*/
rpc GetAddressSqueakDisplays (GetAddressSqueakDisplaysRequest) returns (GetAddressSqueakDisplaysReply) {}
/** sqkadmin: `getaddresssqueakdisplays`
*/
rpc GetAncestorSqueakDisplays (GetAncestorSqueakDisplaysRequest) returns (GetAncestorSqueakDisplaysReply) {}
}
message HelloRequest {
@ -226,3 +230,13 @@ message GetAddressSqueakDisplaysReply {
/// Multiple squeak display entries
repeated SqueakDisplayEntry squeak_display_entries = 1;
}
message GetAncestorSqueakDisplaysRequest {
/// Hash of the squeak.
string squeak_hash = 1;
}
message GetAncestorSqueakDisplaysReply {
/// Multiple squeak display entries
repeated SqueakDisplayEntry squeak_display_entries = 1;
}

View file

@ -96,3 +96,14 @@ class SqueakAdminServerHandler(object):
max_block,
)
return squeak_entries_with_profile
def handle_get_ancestor_squeak_display_entries(self, squeak_hash_str):
logger.info("Handle get ancestor squeak display entries for squeak hash: {}".format(squeak_hash_str))
squeak_entries_with_profile = self.squeak_node.get_ancestor_squeak_entries_with_profile(
squeak_hash_str,
)
logger.info("Got number of ancestor squeak entries: {}".format(len(squeak_entries_with_profile)))
logger.info("Got ancestor squeak display entries:")
for entry in squeak_entries_with_profile:
logger.info("Entry: {}".format(entry))
return squeak_entries_with_profile

View file

@ -7,7 +7,7 @@ import grpc
from proto import squeak_admin_pb2, squeak_admin_pb2_grpc
from squeakserver.server.util import get_hash
from squeakserver.server.util import get_hash, get_replyto
logger = logging.getLogger(__name__)
@ -132,6 +132,20 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
squeak_display_entries=squeak_display_msgs
)
def GetAncestorSqueakDisplays(self, request, context):
squeak_hash_str = request.squeak_hash
squeak_entries_with_profile = self.handler.handle_get_ancestor_squeak_display_entries(
squeak_hash_str,
)
squeak_display_msgs = [
self._squeak_entry_to_message(entry)
for entry in
squeak_entries_with_profile
]
return squeak_admin_pb2.GetFollowedSqueakDisplaysReply(
squeak_display_entries=squeak_display_msgs
)
def _squeak_entry_to_message(self, squeak_entry_with_profile):
if squeak_entry_with_profile is None:
return None
@ -144,6 +158,8 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
is_author_known = squeak_profile is not None
author_name = squeak_profile.profile_name if squeak_profile else None
author_address = str(squeak.GetAddress())
is_reply = squeak.is_reply
reply_to = get_replyto(squeak).hex() if is_reply else None
return squeak_admin_pb2.SqueakDisplayEntry(
squeak_hash=get_hash(squeak).hex(),
is_unlocked=squeak.HasDecryptionKey(),
@ -153,6 +169,8 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
is_author_known=is_author_known,
author_name=author_name,
author_address=author_address,
is_reply=is_reply,
reply_to=reply_to,
)
def _squeak_profile_to_message(self, squeak_profile):

View file

@ -161,3 +161,8 @@ class SqueakNode:
min_block,
max_block,
)
def get_ancestor_squeak_entries_with_profile(self, squeak_hash_str):
return self.postgres_db.get_thread_ancestor_squeak_entries_with_profile(
squeak_hash_str,
)

View file

@ -50,7 +50,7 @@ class PostgresDb:
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, address, vch_decryption_key)
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;"""
@ -98,7 +98,7 @@ class PostgresDb:
sql = """
SELECT * FROM squeak
LEFT JOIN profile
ON squeak.address=profile.address
ON squeak.author_address=profile.address
WHERE squeak.hash=%s
"""
@ -114,7 +114,7 @@ class PostgresDb:
sql = """
SELECT * FROM squeak
JOIN profile
ON squeak.address=profile.address
ON squeak.author_address=profile.address
WHERE squeak.block_header IS NOT NULL
AND profile.following=False
ORDER BY n_block_height DESC, n_time DESC;
@ -129,9 +129,9 @@ class PostgresDb:
sql = """
SELECT * FROM squeak
JOIN profile
ON squeak.address=profile.address
ON squeak.author_address=profile.address
WHERE squeak.block_header IS NOT NULL
AND squeak.address=%s
AND squeak.author_address=%s
AND n_block_height >= %s
AND n_block_height <= %s
ORDER BY n_block_height DESC, n_time DESC;
@ -141,11 +141,32 @@ class PostgresDb:
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(n) AS (
VALUES(%s)
UNION\n
SELECT hash_reply_sqk FROM squeak, is_thread_ancestor
WHERE squeak.hash=is_thread_ancestor.n
)
SELECT * FROM squeak
JOIN is_thread_ancestor
ON squeak.hash=is_thread_ancestor.n
LEFT JOIN profile
ON squeak.author_address=profile.address
WHERE squeak.block_header IS NOT NULL;
"""
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):
""" Lookup squeaks. """
sql = """
SELECT hash FROM squeak
WHERE address IN %s
WHERE author_address IN %s
AND n_block_height >= %s
AND n_block_height <= %s
AND vch_decryption_key IS NOT NULL

View file

@ -7,6 +7,10 @@ def get_hash(squeak):
return squeak.GetHash()[::-1]
def get_replyto(squeak):
return squeak.hashReplySqk
def generate_offer_nonce():
return os.urandom(DATA_KEY_LENGTH)