Skip to content
This repository has been archived by the owner on Nov 15, 2022. It is now read-only.

Trying to deserialize Signed Transaction with Program #1

Open
giekaton opened this issue Sep 26, 2019 · 31 comments
Open

Trying to deserialize Signed Transaction with Program #1

giekaton opened this issue Sep 26, 2019 · 31 comments

Comments

@giekaton
Copy link

Hi and thanks for the library.

I'm trying to deserialize a signed transaction with the program, but getting the following error, maybe you see what's wrong and can help me.

Error:

...Python\Python37\site-packages\canoser\rust_enum.py", line 69, in decode
    _name, datatype = cls._enums[index]
IndexError: list index out of range

My signed txn is this:
signed_txn = "200000003A24A61E05D129CACE9E0EFC8BC9E33831FEC9A9BE66F50FD352A2638A49B9EE200000000000000000000000040000006D6F766502000000020000000900000043414645204430304402000000090000006361666520643030640300000001000000CA02000000FED0010000000D1027000000000000204E0000000000008051010000000000"

I have defined structs like this:

class Addr(Struct):
    _fields = [
      ('addr', [Uint8, 32])
    ]

class TransactionArgument(RustEnum):
    _enums = [
        ('U64', Uint64),
        ('Address', Addr),
        ('ByteArray', [Uint8]),
        ('String', str),
    ]

class TransactionProgram(Struct):
    _fields = [
        ( 'code', [Uint8]),
        ( 'args', TransactionArgument),
        ( 'modules', [Uint8] ),
    ]

class Raw(Struct):
    _fields = [
        ('sender', Addr),
        ('sequence_number', Uint64),
        ('payload', TransactionProgram),
        ('max_gas_amount', Uint64),
        ('gas_unit_price', Uint64),
        ('expiration_time', Uint64),
    ]

And the rest of the code:

def test():
    txn_bytes = bytes.fromhex(signed_txn)
    result = Raw.deserialize(txn_bytes)
    print(result)
test()
@giekaton giekaton changed the title Trying to deserialize Signed Transaction with Proof Trying to deserialize Signed Transaction with a Program Sep 26, 2019
@giekaton giekaton changed the title Trying to deserialize Signed Transaction with a Program Trying to deserialize Signed Transaction with Program Sep 26, 2019
@yuan-xy
Copy link
Owner

yuan-xy commented Sep 26, 2019 via email

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 26, 2019

@giekaton
Copy link
Author

Hi, thank you for the references and documentation updates. I'm learning and trying to run the LCS, but not quite there yet.

I see that in your transaction.py script, there are all details defined for the signed transaction deserialization.

So I'm using your transaction.py file, at the bottom I'm adding my custom code:

signed_txn = "200000003A24A61E05D129CACE9E0EFC8BC9E33831FEC9A9BE66F50FD352A2638A49B9EE200000000000000000000000040000006D6F766502000000020000000900000043414645204430304402000000090000006361666520643030640300000001000000CA02000000FED0010000000D1027000000000000204E0000000000008051010000000000"

txn_bytes = bytes.fromhex(signed_txn)
result = SignedTransaction.deserialize(txn_bytes)

print(result)

When I run it, I get the error:

...Python\Python37\site-packages\canoser\cursor.py", line 9, in read_bytes
    raise IOError("{} exceed buffer size: {}".format(end, total))
OSError: 142 exceed buffer size: 138

Maybe it's because my txn is a RawTransaction with a Program (as in Libra LCS specification), and not with a WriteSet. Do I need to specify this in structs? If not, any ideas what I'm doing wrong?

@giekaton
Copy link
Author

The above signed_txn is a RawTransaction with a Program example from https://github.com/libra/libra/tree/master/common/canonical_serialization

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 26, 2019

Maybe you can tell me the transaction id, so I can figure out what's the problem there.

@giekaton
Copy link
Author

Please check my previous comment. It's an example transaction from the readme. At the very bottom of that page it's deserialized.

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 26, 2019

You are right. The above signed_txn is a RawTransaction.

txn_bytes = bytes.fromhex(signed_txn)
result = libra.RawTransaction.deserialize(txn_bytes)
print(result)

It works fine.

@giekaton
Copy link
Author

giekaton commented Sep 26, 2019

It works with RawTransaction.deserial... (without "libra." at the beginning)

When I print the deserialized result, I get the below message. How can I turn it into an object like representation, as it is in the LCS readme? I'm a Python newbie ツ

I will need to return it to my Node.js client from a spawned Python module.

<__main__.RawTransaction object at 0x000001522541BF08>

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 26, 2019

I'm writing a str method to pretty print this object, comming soon

@giekaton
Copy link
Author

Thank you very much. Looking forward to it!

Also, maybe this is something you know, and it's an easy answer.

To query a custom txn by range, I use a grpc client and the returned signed_txn looks like this:

signed_txn: "IAAAAB/HpLI25vah9LY90dFahBXFrtJYvHxrTT2+jxUQSw6SAQAAAAAAAAACAAAAuAAAAExJQlJBVk0KAQAHAUoAAAAEAAAAA04AAAAGAAAADVQAAAAGAAAADloAAAAGAAAABWAAAAApAAAABIkAAAAgAAAACKkAAAAPAAAAAAAAAQACAAEDAAIAAgQCAAMCBAIDAAY8U0VMRj4MTGlicmFBY2NvdW50BG1haW4PcGF5X2Zyb21fc2VuZGVyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQACAAQADAAMARMBAQICAAAAAQAAACAAAACbF/h+UR+X4dxOXN5KqU17lMJVNBEMJsl85hiXNVgvwAAAAABAQg8AAAAAAOAiAgAAAAAAAAAAAAAAAAB5KotdAAAAACAAAADGMHrvNmuIrxuW5cmzptu7rmo/bCOohwQSSpRMv/zmdkAAAADKZ+SsmtKC07wa7vK3nYVdkkeoLEmrpxCr/zw6T50jmB6TNp0XNpCT8D1p6JSQ5TlOYEyPvjZjl3yS0lHmps0D"

This doesn't look like the LCS format and it doesn't work with bytes.fromhex(signed_txn).

Does it need to be first decoded into an LCS representation, or is it more likely that my grpc client is returning it in the wrong format?

When querying directly from Rust CLI, the same txn looks like below:

raw_txn: RawTransaction {
        sender: 1fc7a4b..4b0e92,
        sequence_number: 1,
        payload: {,
                transaction: peer_to_peer_transaction,
                args: [
                        {ADDRESS: 9b17...82fc0},
                        {U64: 1000000},
                ]
        },
        max_gas_amount: 140000,
        gas_unit_price: 0,
        expiration_time: 1569401465s,
},
 public_key: Ed25519PublicKey(
    PublicKey(CompressedEdwardsY: [198, 48, 122, 239, 54, ...
 )
 signature: Ed25519Signature(
    Signature( R: CompressedEdwardsY: [202, 103, 22 ...
 )

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 27, 2019

Update canoser to 0.3.4, add simple pretty print support.

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 27, 2019

after get a proto class txn, you can get the SignedTransaction object with:

stx = SignedTransaction.deserialize(txn.signed_txn)

you can refer to this code:
test_get_transaction of libra-client

@giekaton
Copy link
Author

Pretty printing works great. 非常感谢你.

And thanks for the reference regarding the deserialization of the signed_txn. I think I will finish everything tomorrow, now time sleep.

@giekaton
Copy link
Author

Hi, regarding my second question, in your file test_client.py, I see that your client gets the signed_txn and decodes it here:

def test_get_transaction():
    c = libra.Client("testnet")
    txn = c.get_transaction(1)
    assert len(txn.signed_txn) > 0
    stx = SignedTransaction.deserialize(txn.signed_txn)

The signed_txn returned from my Node.js grpc client is in the format as I mentioned above and if I try to deserialize it with SignedTransaction.deserialize(signed_txn), I get this error: TypeError: a bytes-like object is required, not 'str'.

Any ideas?

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 27, 2019 via email

@giekaton
Copy link
Author

Can I convert it to bytes with Python, or should the grpc client return it in the right format?

Below in the comments you can see the errors I get when trying to decode it from string.

signed_txn = "IAAAAB/HpLI25vah9LY90dFahBXFrtJYvHxrTT2+jxUQSw6SAQAAAAAAAAACAAAAuAAAAExJQlJBVk0KAQAHAUoAAAAEAAAAA04AAAAGAAAADVQAAAAGAAAADloAAAAGAAAABWAAAAApAAAABIkAAAAgAAAACKkAAAAPAAAAAAAAAQACAAEDAAIAAgQCAAMCBAIDAAY8U0VMRj4MTGlicmFBY2NvdW50BG1haW4PcGF5X2Zyb21fc2VuZGVyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQACAAQADAAMARMBAQICAAAAAQAAACAAAACbF/h+UR+X4dxOXN5KqU17lMJVNBEMJsl85hiXNVgvwAAAAABAQg8AAAAAAOAiAgAAAAAAAAAAAAAAAAB5KotdAAAAACAAAADGMHrvNmuIrxuW5cmzptu7rmo/bCOohwQSSpRMv/zmdkAAAADKZ+SsmtKC07wa7vK3nYVdkkeoLEmrpxCr/zw6T50jmB6TNp0XNpCT8D1p6JSQ5TlOYEyPvjZjl3yS0lHmps0D"

signed_txn_bytes = bytes.fromhex(signed_txn)
# ERROR: ValueError: non-hexadecimal number found in fromhex() arg at position 0

# signed_txn_bytes = signed_txn.encode()
# The deserializer accepts the encoded string, but it is not in the LCS format, so returns the ERROR: TypeError: 1094795593 is not equal to predefined value: 32

des = SignedTransaction.deserialize(signed_txn_bytes)
print(des)

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 27, 2019 via email

@giekaton
Copy link
Author

giekaton commented Sep 27, 2019

That's how my Node.js libra-grpc client returns it. I updated my .proto files, but maybe something is still missing, I need to double-check then, or maybe switch to your client.

I will also double-check the struct.unpack, I guess I need to refer to the original Libra Rust source code, or are there Python unpack examples in your client?

@giekaton
Copy link
Author

I rebuilt the .proto files from another Libra branch, but the signed_txn is returned in the same format as before. I guess grpc client works fine, but the unpacking is what's necessary.

In your code, regarding struct.unpack there is only bytes to int_list unpacking:

def bytes_to_int_list(bytes_str):
    tp = struct.unpack("<{}B".format(len(bytes_str)), bytes_str)
    return list(tp)

So for my signed_txn unpacking, any ideas?

def unpack_signed_txn(???):
    txn = struct.unpack( ???, ??? )
    return print(txn)

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 28, 2019 via email

@giekaton
Copy link
Author

It works!!! Thanks a lot. I just need to finish with the decoding.

{
  raw_txn: {
    sender: [31, 199, 164, 178, 54, 230, 246, 161, 244, 182, 61, 209, 209, 90, 132, 21, 197, 174, 210, 88, 188, 124, 107, 77, 61, 190, 143, 21, 16, 75, 14, 146],
    sequence_number: 1,
    payload: Script{
      code: [76, 73, 66, 82, 65, 86, 77, 10, 1, 0, 7, 1, 74, 0, 0, 0, 4, 0, 0, 0, 3, 78, 0, 0, 0, 6, 0, 0, 0, 13, 84, 0, 0, 0, 6, 0, 0, 0, 14, 90, 0, 0, 0, 6, 0, 0, 0, 5, 96, 0, 0, 0, 41, 0, 
0, 0, 4, 137, 0, 0, 0, 32, 0, 0, 0, 8, 169, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 3, 0, 2, 0, 2, 4, 2, 0, 3, 2, 4, 2, 3, 0, 6, 60, 83, 69, 76, 70, 62, 12, 76, 105, 98, 114, 97, 65, 99, 99, 111, 117, 110, 116, 4, 109, 97, 105, 110, 15, 112, 97, 121, 95, 102, 114, 111, 109, 95, 115, 101, 110, 100, 101, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 4, 0, 12, 0, 12, 1, 19, 1, 1, 2],
      args: [<__main__.TransactionArgument object at 0x0000021F6AAEECC8>, <__main__.TransactionArgument object at 0x0000021F6AAEEEC8>],
    },
    max_gas_amount: 140000,
    gas_unit_price: 0,
    expiration_time: 1569401465,
  },
  public_key: [198, 48, 122, 239, 54, 107, 136, 175, 27, 150, 229, 201, 179, 166, 219, 187, 174, 106, 63, 108, 35, 168, 135, 4, 18, 74, 148, 76, 191, 252, 230, 118],
  signature: [202, 103, 228, 172, 154, 210, 130, 211, 188, 26, 238, 242, 183, 157, 133, 93, 146, 71, 168, 44, 73, 171, 167, 16, 171, 255, 60, 58, 79, 157, 35, 152, 30, 147, 54, 157, 23, 54, 144, 147, 240, 61, 105, 232, 148, 144, 229, 57, 78, 96, 76, 143, 190, 54, 99, 151, 124, 146, 210, 81, 230, 166, 205, 3],
}

@giekaton
Copy link
Author

To decode sender, it works like this: print(int_list_to_hex(txn.raw_txn.sender))

But the args also need to be pretty-printed. If you can update your code, so that it pretty prints also the args, that would be great.

@yuan-xy
Copy link
Owner

yuan-xy commented Sep 28, 2019 via email

@giekaton
Copy link
Author

Thanks, looking forward to it, that's the only part I'm missing. Meanwhile, I will finish integrating this LCS as a spawned child process in my Node.js grpc client. It already works, just need to run it on the real testnet. I will later share the code on GitHub.

@yuan-xy
Copy link
Owner

yuan-xy commented Oct 1, 2019

The code of address formatting is updated.

@giekaton
Copy link
Author

giekaton commented Oct 2, 2019

Hi, thanks a lot, works great. I have already integrated your LCS into Libra Checker. I still need to finish some things, then will update the GitHub.

As far as I know, there is yet no LCS written in JavaScript, so your Python LCS running in Node.js as a child process is a cool workaround.

@yuan-xy
Copy link
Owner

yuan-xy commented Oct 3, 2019

Maybe I can port Canoser from python to JavaScript.

@giekaton
Copy link
Author

giekaton commented Oct 3, 2019

There are no solutions yet:
perfectmak/libra-core#48
https://community.libra.org/t/rawtransaction-object-removed-from-transaction-proto/1771

So I guess LCS in JS would be very useful for the community.

@giekaton
Copy link
Author

giekaton commented Nov 4, 2019

Hi Yuan,

I'm still using Canoser and following your code on libra-client. Great work, much appreciated.

Can you help me to decode the deserialized txn payload's "code" field?

My current python file looks like this:
https://gist.github.com/giekaton/ff139bd81c1d041aa300b572b67ca8a9

Example query: Transaction nr 44 (the result from Canoser is below)

I need to extract the transaction_type from the code field.

UserTransaction: {
  raw_txn: {
    sender: 000000000000000000000000000000000000000000000000000000000a550c18,
    sequence_number: 43,
    payload: Script: {
      code: 4c49425241564d0a010007014a000000060000000350000000060000000d56000000060000000e5c0000000600000005620000003300000004950000002000000008b50000000f000000000000010002000300010400020002040200030204020300063c53454c463e0c4c696272614163636f756e74094c69627261436f696e046d61696e0f6d696e745f746f5f616464726573730000000000000000000000000000000000000000000000000000000000000000000100020004000c000c0113010102,
      args: [
        Address: dfef62fa6d938c838d4bbbcf394fe4e5d96392578efaa338d0510bb66bff7167,
        U64: 100000000,
      ],
    },
    max_gas_amount: 140000,
    gas_unit_price: 0,
    expiration_time: 1572491151,
  },
  public_key: 664f6e8f36eacb1770fa879d86c2c1d0fafea145e84fa7d671ab7a011a54d509,
  signature: 74366a7d290f8dfa0de3e500b7683b74db33d17ceade19c4cc553b2b1bb8d2e148e0409a7c79ed2e089c2ff4b74189f2f9ef5840cab8af81a64b30a4b7918703,
}

@yuan-xy
Copy link
Owner

yuan-xy commented Nov 5, 2019

I'm currently writing an openAPI for Libra, you may use the API to solve the problem later. But now, you can do like this:

from libra.bytecode import bytecodes

#tx = tx.to_json_serializable()
#payload = tx['raw_txn']['payload']

def get_tx_abbreviation_name(payload, version):
    if version == 0:
        return "Genesis"
    try:
        payload['Script'] = payload.pop('Program')
    except KeyError:
        pass
    if list(payload)[0] != "Script":
        return list(payload)[0]
    code = hex_to_int_list(payload['Script']['code'])
    if code == bytecodes["mint"]:
        return "mint"
    if code == bytecodes["peer_to_peer_transfer"]:
        return "p2p"
    if code == bytecodes["create_account"]:
        return "new account"
    if code == bytecodes["rotate_authentication_key"]:
        return "rotate key"
    return "script"

@giekaton
Copy link
Author

giekaton commented Nov 5, 2019

It works and I learned a lot from your code snippet. Thanks. Looking forward to the openAPI.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants