2
  1. Where is the information on the tx package build protocol? Bitcoin as an example, Bitcoin Wiki - Protocol Documentation: tx

  2. Is there an example of serialization for python like this answer to How to redeem a basic Tx?

All that was found in Google, were using different libraries.

5
  • The formal definition is in Ethereum's Yellow Paper section 4.2 Transaction. In particular L_T(T).
    – Ismael
    Commented May 10, 2019 at 16:00
  • For sure! Yellow paper! At Ethereum it is unusually decorated. I'll try to make a test dump. Commented May 10, 2019 at 18:10
  • I wrote a serialization module. Made a test transaction. The last question left is how to calculate the values ​​of gasPrice, gasLimit? The first time I posted test values ​​gasPrice = 50000 gasLimit = 21000. But my need more meaningfulness. Commented May 16, 2019 at 9:57
  • If you search in the site those questions were already asked a few times. For more details it is better to create a new question.
    – Ismael
    Commented May 16, 2019 at 13:29
  • This link explains it very good: medium.com/@codetractio/… Commented Aug 26, 2022 at 15:36

1 Answer 1

2

1) Ismael replied in the comments that the information is available in Yellow Paper

2) From the Ethereum yellow paper, we know the a transaction's logical structure is as follows.

-----------------------------
| Nonce    | Up to 32 bytes |
-----------------------------
| GasPrice | Up to 32 bytes |
-----------------------------
| GasLimit | Up to 32 bytes |
-----------------------------
| To       | 20 bytes addr  |
-----------------------------
| Value    | Up to 32 bytes |
-----------------------------
| Data     | 0 - unlimited  |
-----------------------------
| V        | 1 (usually)    |
-----------------------------
| R        | 32 bytes       |
-----------------------------
| S        | 32 bytes       |
-----------------------------

Note:

  1. This is only the logic structure. The actually data is encoded in RLP format, thus is longer due to the added length prefix.
  2. The V field was always 1 byte before EIP-155. It's probably safe to say every major client has implemented EIP-155. For the main net, test net, this field stays as 1 byte even with EIP-155. For private networks with "chain ID" of larger values, this field can be much longer. See this question.

Take for example the transaction from this hash:

0x14a298c1eea89f42285948b7d51eeac2876ca7406c9784b9b90dd3591d156d64

Out:

"0xf86b80850ba43b7400825208947917bc33eea648809c285607579c9919fb864f8f8703baf82d03a0008025a0067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46a069b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69"

which is 109 bytes. If we parse the data

f86b length
80 nonce (0: this is the minimum an account can have)
85 0ba43b7400 gas price
82 5208 gas limit (this is fixed for simple payments)
94 7917bc33eea648809c285607579c9919fb864f8f (address, always 20 bytes)
87 03baf82d03a000 (value, in theory this can be shrunken to zero)
80 (data, already zero length)
25 (V, one byte)
a0 067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46 (R)
a0 69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69 (S)

Now I will try to use the RLP library in order to select the structure.

import rlp
tx_message = list()

# tx_len - f8
tx_nonce = ''
tx_gasPrice = 0x0ba43b7400
tx_gasLimit = 0x5208
tx_to = 0xcce5fd90eabab3d5d35119eed7f2ac5796e3d06c
tx_value = 0x03baf82d03a000
tx_data = 0x00
tx_w = 0x25
tx_r = 0x067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46
tx_s = 0x69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69

tx_message.extend(
    (
        rlp.encode(tx_nonce),
        rlp.encode(tx_gasPrice),
        rlp.encode(tx_gasLimit),
        rlp.encode(tx_to),
        rlp.encode(tx_value),
        rlp.encode(tx_data),
        rlp.encode(tx_w),
        rlp.encode(tx_r),
        rlp.encode(tx_s),
    )
)

result_b = b''.join(tx_message)
result = result_b.hex()

I wanted to show the code above that it is possible to choose a structure, but this is not yet true, because we do not consider the size of the transaction and do not sign it. Now we will rewrite the working code.

class Transaction(rlp.Serializable):
    fields = [
        ('nonce', big_endian_int),
        ('gasprice', big_endian_int),
        ('startgas', big_endian_int),
        ('to', Binary.fixed_length(20, allow_empty=True)),
        ('value', big_endian_int),
        ('data', binary),
        ('v', big_endian_int),
        ('r', big_endian_int),
        ('s', big_endian_int),
    ]

    _sender = None

    def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0):
        # self.data = None
        to = normalize_address(to, allow_blank=True)
        super(Transaction, self).__init__(nonce, gasprice, startgas, to, value, data, v, r, s)

        if gasprice >= TT256 or startgas >= TT256 or value >= TT256 or nonce >= TT256:
            logging.error("Values way too high!")

    def sign(self, key, network_id=None):
        """
        Sign this transaction with a private key.
        A potentially already existing signature would be overridden.
        """
        if network_id is None:
            rawhash = sha3(
                rlp.encode(
                    unsigned_tx_from_tx(self),
                    UnsignedTransaction
                )
            )
        else:
            assert 1 <= network_id < 2 ** 63 - 18
            rawhash = sha3(
                rlp.encode(
                    rlp.infer_sedes(self).serialize(self)[:-3] +
                    [network_id, b'', b'']
                )
            )

        key = normalize_key(key)
        v, r, s = ecsign(rawhash, key)

        if network_id is not None:
            self.v += 8 + network_id * 2

        ret = self.copy(v=v,
                        r=r,
                        s=s)
        ret._sender = privtoaddr(key)

        return ret


class UnsignedTransaction(rlp.Serializable):
    fields = []
    for field, sedes in Transaction._meta.fields:
        if field not in "vrs":
            fields.append((field, sedes))


def unsigned_tx_from_tx(tx):
    return UnsignedTransaction(
        nonce=tx.nonce,
        gasprice=tx.gasprice,
        startgas=tx.startgas,
        to=tx.to,
        value=tx.value,
        data=tx.data,
    )

def fetch_url_json_path_int(url, path):
    def func():
        request = req.Request(url, headers={'User-Agent': 'wallet'})
        try:
            payload = req.urlopen(request).read()
        except Exception as e:
            logging.error(f'[fetch_url_json_path_int] {e}')

        try:
            data = loads(payload)
            for component in path.split('/'):
                if isinstance(data, dict):
                    data = data[component]
                elif isinstance(data, (list, tuple)):
                    data = data[int(component)]
            return data
        except Exception as e:
            return data
    return func

def get_tx_count(address):
    return fetch_url_json_path_int(f'{url_tx_count}{address}', 'result')()

url_tx_count = 'https://api.etherscan.io/api?module=proxy&action=eth_getTransactionCount&address='

private_key = hashlib.sha256('This keyword!!!'.encode()).hexdigest()
nonce = int(get_tx_count('0xcce5fd90eabab3d5d35119eed7f2ac5796e3d06c'), 16)
gasPrice = 28500000000
gasLimit = 21000
to = 0x77f5055E19247E091e0C5bb3483190F9E6E43d3f
value = 0
data = codecs.decode('', 'hex')


transaction = Transaction(
    nonce=nonce,
    gasprice=gasPrice,
    startgas=gasLimit,
    to=to,
    value=value,
    data=data,
).sign(private_key)

print(rlp.encode(transaction))

Func send to:

url_broadcast_transaction = 'https://api.blockcypher.com/v1/eth/main/txs/push'


def broadcast_transaction(message):
    timeout_in_second = 10
    data = {'tx': message.hex()}
    params = {'token': None}
    r = requests.post(
        url=url_broadcast_transaction,
        json=data,
        params=params,
        verify=True,
        timeout=timeout_in_second
    )
    logging.debug(r.text)
    return r.text

Here you can see the missing modules from the code.

The most important! gasLimit will be equal to 21000 for the transaction, but not for the contract. gasPrice need to get here this link. After receiving gasPrice, it needs to be converted from GWei to Wei

6
  • 1
    I am afraid the transaction you use as an example was crafted by me. You are welcome to share but it would be nice to at least have proper reference, as the writing is clearly based on my content here lsongnotes.wordpress.com/2018/01/14/… Commented Jun 27, 2019 at 16:38
  • @LinmaoSong Thank you for your work, +1 Commented May 14, 2020 at 6:31
  • @LinmaoSong it's actually funny I bumped into you here since I am in the middle of porting your very tutorial to JS --- I am running into a slight issue with ECDSA signing though, I've posted a formal question in another thread so as not to clutter this one, do you think you could take a look at it? ethereum.stackexchange.com/questions/83413/… Commented May 14, 2020 at 21:03
  • @LinmaoSong Perhaps you are right, I probably studied the information on your page, but the material presented here is more complete. Commented May 26, 2020 at 14:45
  • This answer should be updated to reflect the latest implementation, which also includes chain ID as an RLP parameter. Commented Jun 8, 2021 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.