Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding python module to get validator information #10

Merged
merged 1 commit into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pactus-*
cmd/data/wallet.json
cmd/data/config.json
cmd/data/validators.json
cmd/bin
cmd/bin
__pycache__
66 changes: 66 additions & 0 deletions python/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import io
from enum import Enum
import utils

# Address format: hrp + `1` + type + data + checksum


class AddressType(Enum):
Treasury = 0
Validator = 1
BLSAccount = 2


AddressSize = 21
TreasuryAddressString = "000000000000000000000000000000000000000000"
AddressHRP = "tpc"


class Address:
def __init__(self, address_type, data):
if len(data) != AddressSize - 1:
raise ValueError("Data must be 21 bytes long")

self.data = bytearray()
self.data.append(address_type.value)
self.data.extend(data)

@classmethod
def from_string(cls, text):
if text == TreasuryAddressString:
return bytes([0])

hrp, typ, data = utils.decode_to_base256_with_type(text)
if hrp != AddressHRP:
raise ValueError(f"Invalid HRP: {hrp}")

typ = AddressType(typ)
if typ in (AddressType.Validator, AddressType.BLSAccount):
if len(data) != 20:
raise ValueError(f"Invalid length: {len(data) + 1}")
else:
raise ValueError(f"Invalid address type: {typ}")

return cls(typ, data)

def bytes(self):
return bytes(self.data)

def string(self):
if self.data == bytes([0]):
return TreasuryAddressString

return utils.encode_from_base256_with_type(AddressHRP, self.data[0], self.data[1:])

def address_type(self):
return AddressType(self.data[0])

def is_treasury_address(self):
return self.address_type() == AddressType.Treasury

def is_account_address(self):
t = self.address_type()
return t in (AddressType.Treasury, AddressType.BLSAccount)

def is_validator_address(self):
return self.address_type() == AddressType.Validator
137 changes: 137 additions & 0 deletions python/bech32m.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Copyright (c) 2017, 2020 Pieter Wuille
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""Reference implementation for Bech32/Bech32m and segwit addresses."""


from enum import Enum

class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2

CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32M_CONST = 0x2bc830a3

def bech32_polymod(values):
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk


def bech32_hrp_expand(hrp):
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]


def bech32_verify_checksum(hrp, data):
"""Verify a checksum given HRP and converted data characters."""
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
if const == 1:
return Encoding.BECH32
if const == BECH32M_CONST:
return Encoding.BECH32M
return None

def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
const = BECH32M_CONST if spec == Encoding.BECH32M else 1
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]


def bech32_encode(hrp, data, spec):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])

def bech32_decode(bech):
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return (None, None, None)
bech = bech.lower()
pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech):
return (None, None, None)
if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)

def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret


def decode(hrp, addr):
"""Decode a segwit address."""
hrpgot, data, spec = bech32_decode(addr)
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
return (None, None)
if data[0] > 16:
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M:
return (None, None)
return (data[0], decoded)


def encode(hrp, witver, witprog):
"""Encode a segwit address."""
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
if decode(hrp, ret) == (None, None):
return None
return ret
61 changes: 61 additions & 0 deletions python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import grpc
import sys
import json
# Why can't??
# from pactus import network_pb2
# from pactus import network_pb2_grpc

import public_key
import network_pb2
import network_pb2_grpc


def load_json_file(file_path):
try:
with open(file_path, 'r') as file:
data = json.load(file)
return data
except FileNotFoundError:
print(f"File not found: {file_path}")
return None
except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
return None


def update_validator_map(url, valMap):
channel = grpc.insecure_channel(url)
stub = network_pb2_grpc.NetworkStub(channel)
req = network_pb2.GetNetworkInfoRequest()
info = stub.GetNetworkInfo(req)

for peer in info.peers:
for i, key in enumerate(peer.consensus_keys):
pub = public_key.PublicKey.from_string(key)
valAddr = pub.validator_address().string()
valMap[valAddr] = i

if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python main.py <file_path>")
else:
file_path = sys.argv[1]
json_data = load_json_file(file_path)

valMap = {}
update_validator_map('172.104.46.145:50052', valMap)
update_validator_map('94.101.184.118:50052', valMap)
update_validator_map('51.158.118.181:50052', valMap)
update_validator_map('172.232.108.191:50052', valMap)

for key, value in json_data.items():
userValAddr = value['validator_address']
index = valMap.get(userValAddr, None)
if index is not None:
if index != 0:
print("user {} staked in wrong validator. index: {}".format(
value['discord_name'], index))
else:
print("unable to find validator {} information for user {}".format(
value['validator_address'],
value['discord_name']))
52 changes: 52 additions & 0 deletions python/network_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading