Skip to content

Instantly share code, notes, and snippets.

@giekaton
Created November 4, 2019 18:20
Show Gist options
  • Save giekaton/ff139bd81c1d041aa300b572b67ca8a9 to your computer and use it in GitHub Desktop.
Save giekaton/ff139bd81c1d041aa300b572b67ca8a9 to your computer and use it in GitHub Desktop.
import pdb
from canoser import *
from datetime import datetime
import base64
import struct
import json
from hasher import HashValue
ADDRESS_LENGTH = 32
ED25519_PUBLIC_KEY_LENGTH = 32
ED25519_SIGNATURE_LENGTH = 64
class BlockMetadata(Struct):
_fields = [
('id', HashValue),
('timestamp_usec', Uint64),
#('previous_block_votes', {Address, [Uint8, ED25519_SIGNATURE_LENGTH]}),
('previous_block_votes', {}),
('proposer', [Uint8, ADDRESS_LENGTH, False])
]
class TransactionArgument(RustEnum):
_enums = [
('U64', Uint64),
('Address', [Uint8, ADDRESS_LENGTH, False]),
('String', str),
('ByteArray', [Uint8])
]
class WriteOp(RustEnum):
_enums = [
('Deletion', None),
('Value', [Uint8])
]
class AccessPath(Struct):
_fields = [
('address', [Uint8, ADDRESS_LENGTH, False]),
('path', [Uint8])
]
class Program(Struct):
_fields = [
('code', [Uint8]),
('args', [TransactionArgument]),
('modules', [[Uint8]])
]
# `WriteSet` contains all access paths that one transaction modifies. Each of them is a `WriteOp`
# where `Value(val)` means that serialized representation should be updated to `val`, and
# `Deletion` means that we are going to delete this access path.
class WriteSet(Struct):
_fields = [
('write_set', [(AccessPath, WriteOp)])
]
class Module(Struct):
_fields = [
('code', [Uint8])
]
class Script(Struct):
_fields = [
('code', [Uint8]),
('args', [TransactionArgument])
]
class TransactionPayload(RustEnum):
_enums = [
('Program', Program),
('WriteSet', WriteSet),
('Script', Script),
('Module', Module)
]
class RawTransaction(Struct):
_fields = [
('sender', [Uint8, ADDRESS_LENGTH, False]),
('sequence_number', Uint64),
('payload', TransactionPayload),
('max_gas_amount', Uint64),
('gas_unit_price', Uint64),
('expiration_time', Uint64)
]
@classmethod
def new_write_set(cls, sender_address, sequence_number, write_set):
return RawTransaction(
sender_address, sequence_number,
TransactionPayload('WriteSet', write_set),
# Since write-set transactions bypass the VM, these fields aren't relevant.
0, 0,
# Write-set transactions are special and important and shouldn't expire.
Uint64.max_value
)
@classmethod
def gen_transfer_transaction(cls, sender_address, sequence_number, receiver_address,
micro_libra, max_gas_amount=140_000, gas_unit_price=0, txn_expiration=100):
if isinstance(sender_address, bytes):
sender_address = bytes_to_int_list(sender_address)
if isinstance(sender_address, str):
sender_address = hex_to_int_list(sender_address)
if isinstance(receiver_address, bytes):
receiver_address = bytes_to_int_list(receiver_address)
if isinstance(receiver_address, str):
receiver_address = hex_to_int_list(receiver_address)
code = cls.get_script_bytecode("peer_to_peer_transfer")
script = Script(
code,
[
TransactionArgument('Address', receiver_address),
TransactionArgument('U64', micro_libra)
]
)
return RawTransaction(
sender_address,
sequence_number,
TransactionPayload('Script', script),
max_gas_amount,
gas_unit_price,
int(datetime.utcnow().timestamp()) + txn_expiration
)
@classmethod
def gen_mint_transaction(cls, receiver, micro_libra):
pass
#TODO:
@staticmethod
def get_script_bytecode(script_name):
return bytecode[script_name]
@staticmethod
def get_script_bytecode_deprecated(script_file):
with open(script_file) as f:
data = f.read()
amap = eval(data)
return amap['code']
class SignedTransaction(Struct):
_fields = [
('raw_txn', RawTransaction),
('public_key', [Uint8, ED25519_PUBLIC_KEY_LENGTH]),
('signature', [Uint8, ED25519_SIGNATURE_LENGTH])
# ('transaction_length', Uint64)
]
def to_json_serializable(self):
amap = super().to_json_serializable()
if hasattr(self, 'transaction_info'):
amap["transaction_info"] = self.transaction_info.to_json_serializable()
if hasattr(self, 'events'):
amap["events"] = [x.to_json_serializable() for x in self.events]
if hasattr(self, 'version'):
amap["version"] = self.version
return amap
@classmethod
def gen_from_raw_txn(cls, raw_tx, sender_account):
tx_hash = raw_tx.hash()
signature = sender_account.sign(tx_hash)[:64]
return SignedTransaction(raw_tx,
bytes_to_int_list(sender_account.public_key),
bytes_to_int_list(signature)
)
def hash(self):
shazer = gen_hasher(b"SignedTransaction")
shazer.update(self.serialize())
return shazer.digest()
@classmethod
def from_proto(cls, proto):
return cls.deserialize(proto.txn_bytes)
def check_signature(self):
message = self.raw_txn.hash()
vkey = VerifyKey(bytes(self.public_key))
vkey.verify(message, bytes(self.signature))
@property
def sender(self):
return self.raw_txn.sender
@property
def sequence_number(self):
return self.raw_txn.sequence_number
@property
def payload(self):
return self.raw_txn.payload
@property
def max_gas_amount(self):
return self.raw_txn.max_gas_amount
@property
def gas_unit_price(self):
return self.raw_txn.gas_unit_price
@property
def expiration_time(self):
return self.raw_txn.expiration_time
class Transaction(RustEnum):
_enums = [
('UserTransaction', SignedTransaction),
('WriteSet', WriteSet),
('BlockMetadata', BlockMetadata)
]
def int_list_to_hex(ints):
return struct.pack("<{}B".format(len(ints)), *ints).hex()
def bytes_to_int_list(bytes_str):
tp = struct.unpack("<{}B".format(len(bytes_str)), bytes_str)
return list(tp)
def hex_to_int_list(hex_str):
return bytes_to_int_list(bytes.fromhex(hex_str))
# passing txn from Node.js
import sys
for line in sys.stdin:
signed_txn_from_grpc = line
signed_txn_bytes = base64.b64decode(signed_txn_from_grpc)
tx = Transaction.deserialize(signed_txn_bytes)
print(tx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment