Skip to content

Commit

Permalink
Add objects to represent the return value from block_info
Browse files Browse the repository at this point in the history
There are a lot fields to still do (and the entire Cert object). This
is a feeler to see if this will satisfy anyone.
  • Loading branch information
jannotti committed Jan 20, 2024
1 parent 3e64019 commit 8b77c93
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 27 deletions.
103 changes: 103 additions & 0 deletions algosdk/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from algosdk import encoding, transaction


class BlockInfo:
def __init__(self, block, cert):
self.block = block
self.cert = cert
pass

@staticmethod
def undictify(d):
return BlockInfo(
encoding.undictify(d.get("block")),
encoding.undictify(d.get("cert")),
)

def __str__(self):
return (
"{"
+ ", ".join(
[
str(key) + ": " + str(value)
for key, value in self.__dict__.items()
]
)
+ "}"
)


class Block:
def __init__(
self,
round,
branch,
seed,
commit,
sha256commit,
timestamp,
genesis_id,
genesis_hash,
counter,
payset,
):
self.round = round
self.branch = branch
self.seed = seed
self.commit = commit
self.sha256commit = sha256commit
self.timestamp = timestamp
self.genesis_id = genesis_id
self.genesis_hash = genesis_hash
self.counter = counter
self.payset = payset

@staticmethod
def undictify(d):
stxns = []
gi = d.get("gen")
gh = d.get("gh")
for stib in d.get("txns", []):
stxn = stib.copy()
stxn["txn"] = stxn["txn"].copy()
if stib.get("hgi", False):
stxn["txn"]["gi"] = gi
# Unconditionally put the genesis hash into the txn. This
# is not strictly correct for very early transactions
# (v15) on testnet. They could have been submitted
# without genhash.
stxn["txn"]["gh"] = gh
stxns.append(transaction.SignedTxnWithAD.undictify(stxn))
return Block(
round=d.get("rnd", 0),
branch=d.get("prev"),
seed=d.get("seed"),
commit=d.get("txn"),
sha256commit=d.get("txn256"),
timestamp=d.get("ts", 0),
genesis_id=d.get("gen"),
genesis_hash=d.get("gh"),
counter=d.get("tc", 0),
payset=stxns,
)

def __str__(self):
return (
"{"
+ ", ".join(
[
str(key) + ": " + str(value)
for key, value in self.__dict__.items()
]
)
+ "}"
)


class Cert:
def __init__(self):
pass

@staticmethod
def undictify(d):
return Cert()
89 changes: 62 additions & 27 deletions algosdk/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import msgpack
from Cryptodome.Hash import SHA512

from algosdk import auction, constants, error, transaction
from algosdk import auction, block, constants, error, transaction


def msgpack_encode(obj):
Expand Down Expand Up @@ -68,32 +68,67 @@ def msgpack_decode(enc):
"""
decoded = enc
if not isinstance(enc, dict):
decoded = msgpack.unpackb(base64.b64decode(enc), raw=False)
if "type" in decoded:
return transaction.Transaction.undictify(decoded)
if "l" in decoded:
return transaction.LogicSig.undictify(decoded)
if "msig" in decoded:
return transaction.MultisigTransaction.undictify(decoded)
if "lsig" in decoded:
if "txn" in decoded:
return transaction.LogicSigTransaction.undictify(decoded)
return transaction.LogicSigAccount.undictify(decoded)
if "sig" in decoded:
return transaction.SignedTransaction.undictify(decoded)
if "txn" in decoded:
return transaction.Transaction.undictify(decoded["txn"])
if "subsig" in decoded:
return transaction.Multisig.undictify(decoded)
if "txlist" in decoded:
return transaction.TxGroup.undictify(decoded)
if "t" in decoded:
return auction.NoteField.undictify(decoded)
if "bid" in decoded:
return auction.SignedBid.undictify(decoded)
if "auc" in decoded:
return auction.Bid.undictify(decoded)

decoded = algo_msgp_decode(base64.b64decode(enc))
return undictify(decoded)

def algo_msgp_decode(enc):
"""Performs msgpack decoding on an Algorand object. Extra care is
taken so that some internal fields that are marked as strings are
decoded without utf-8 processing, because they aren't utf-8. Yet,
we want most string like values to become Python str types for
simplicity.
"""
raw = msgpack.unpackb(enc, raw=True, strict_map_key=False)
return cook(raw)

def cook(raw):
stop = {b"gd", b"ld", b"lg"}
safe = {b"type"}

if isinstance(raw, dict):
cooked = {}
for key, value in raw.items():
v = value if key in stop else cook(value)
v = v.decode() if key in safe else v
if type(key) is bytes:
cooked[key.decode()] = v
else:
cooked[key] = v
return cooked
if isinstance(raw, list):
return [cook(item) for item in raw]
return raw

def undictify(d):
if "type" in d:
return transaction.Transaction.undictify(d)
if "l" in d:
return transaction.LogicSig.undictify(d)
if "msig" in d:
return transaction.MultisigTransaction.undictify(d)
if "lsig" in d:
if "txn" in d:
return transaction.LogicSigTransaction.undictify(d)
return transaction.LogicSigAccount.undictify(d)
if "sig" in d:
return transaction.SignedTransaction.undictify(d)
if "gh" in d: # must proceed next check, since `txn` is in header too, as txn root
return block.Block.undictify(d)
if "txn" in d:
return transaction.Transaction.undictify(d["txn"])
if "subsig" in d:
return transaction.Multisig.undictify(d)
if "txlist" in d:
return transaction.TxGroup.undictify(d)
if "t" in d:
return auction.NoteField.undictify(d)
if "bid" in d:
return auction.SignedBid.undictify(d)
if "auc" in d:
return auction.Bid.undictify(d)
if "block" in d:
return block.BlockInfo.undictify(d)

def is_valid_address(addr):
"""
Expand Down
58 changes: 58 additions & 0 deletions algosdk/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,64 @@ def __eq__(self, other):
and self.authorizing_address == other.authorizing_address
)

class SignedTxnWithAD:
def __init__(
self, stxn: SignedTransaction, apply_data
):
self.stxn = stxn
self.apply_data = apply_data


@staticmethod
def undictify(d):
stxn = SignedTransaction.undictify(d)
ad = ApplyData.undictify(d)
return SignedTxnWithAD(stxn, ad)

class ApplyData:
def __init__(
self,
closing_amount,
asset_closing_amount,
send_rewards,
receiver_rewards,
close_rewards,
eval_delta,
config_asset,
application_id
):
self.closing_amount = closing_amount
self.asset_closing_amount = asset_closing_amount
self.send_rewards = send_rewards
self.receiver_rewards = receiver_rewards
self.close_rewards = close_rewards
self.eval_delta = eval_delta
self.config_asset = config_asset
self.application_id = application_id

@staticmethod
def undictify(d):
return ApplyData(
d.get("ca", 0),
d.get("aca", 0),

d.get("rs", 0),
d.get("rr", 0),
d.get("rc", 0),
EvalDelta.undictify(d.get("dt", {})),
d.get("caid", 0),
d.get("apid", 0),
)

class EvalDelta:
def __init__(
self, gd
):
self.global_delta = gd

@staticmethod
def undictify(d):
return EvalDelta(d.get("gd"))

class MultisigTransaction:
"""
Expand Down
9 changes: 9 additions & 0 deletions algosdk/v2client/algod.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ def block_info(
)
return res

def block(
self,
round: int|str,
**kwargs: Any,
) -> "block.BlockInfo":
msgp = self.block_info(round, "msgpack")
d = encoding.algo_msgp_decode(msgp)
return encoding.undictify(d)

def ledger_supply(self, **kwargs: Any) -> AlgodResponseType:
"""Return supply details for node's ledger."""
req = "/ledger/supply"
Expand Down
11 changes: 11 additions & 0 deletions examples/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ class SandboxAccount:
signer: AccountTransactionSigner


def algod_env():
algodata = os.environ.get("ALGORAND_DATA")
if not algodata:
return ()
try:
token = open(os.path.join(algodata, "algod.token"), "rt").read().strip()
net = "http://"+open(os.path.join(algodata, "algod.net"), "rt").read().strip()
return (net, token)
except FileNotFoundError:
return ()

def get_accounts(
kmd_address: str = KMD_URL,
kmd_token: str = KMD_TOKEN,
Expand Down
18 changes: 18 additions & 0 deletions examples/walk-block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import algosdk.transaction as txn
import algosdk.encoding as enc
from utils import algod_env, get_algod_client

algod = get_algod_client(*algod_env())
latest = algod.status().get("last-round")

# Take an existing SDK return value, and turn it into a nice object
bi = algod.block_info(latest, "msgpack")
block = enc.undictify(enc.algo_msgp_decode(bi))
# walk the payset
for txn in block.block.payset:
print(txn.stxn.transaction.sender)


# Or we can add new calls that coerce the msgp before giving it out
for txn in algod.block(latest - 1).block.payset:
print(txn.stxn.transaction.sender)

0 comments on commit 8b77c93

Please sign in to comment.