¿Cómo construir una transacción simple?

  1. ¿Dónde está la información sobre el protocolo de compilación del paquete tx? Bitcoin como ejemplo, Bitcoin Wiki - Documentación de protocolo: tx

  2. ¿Hay un ejemplo de serialización para python como esta respuesta a Cómo canjear un Tx básico?

Todo lo que se encontró en Google, estaba usando diferentes bibliotecas.

La definición formal se encuentra en la sección 4.2 Transacción del Libro Amarillo de Ethereum. En particular L_T(T).
¡Con seguridad! ¡Papel amarillo! En Ethereum está inusualmente decorado. Intentaré hacer un volcado de prueba.
Escribí un módulo de serialización. Hizo una transacción de prueba. La última pregunta que queda es ¿cómo calcular los valores de gasPrice, gasLimit? La primera vez que publiqué valores de prueba gasPrice = 50000 gasLimit = 21000. Pero necesito más significado.
Si busca en el sitio, esas preguntas ya se hicieron varias veces. Para más detalles es mejor crear una nueva pregunta.
Este enlace lo explica muy bien: medium.com/@codetractio/…

Respuestas (1)

1) Ismael respondió en los comentarios que la información está disponible en Papel Amarillo

2) Por el papel amarillo de Ethereum, sabemos que la estructura lógica de una transacción es la siguiente.

-----------------------------
| 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       |
-----------------------------

Nota:

  1. Esta es sólo la estructura lógica. Los datos reales están codificados en formato RLP, por lo que son más largos debido al prefijo de longitud agregado.
  2. El campo V siempre fue de 1 byte antes de EIP-155. Probablemente sea seguro decir que todos los clientes importantes han implementado EIP-155. Para la red principal, red de prueba, este campo permanece como 1 byte incluso con EIP-155. Para redes privadas con "ID de cadena" de valores más grandes, este campo puede ser mucho más largo. Ver esta pregunta.

Tomemos, por ejemplo, la transacción de este hash:

0x14a298c1eea89f42285948b7d51eeac2876ca7406c9784b9b90dd3591d156d64

Afuera:

"0xf86b80850ba43b7400825208947917bc33eea648809c285607579c9919fb864f8f8703baf82d03a0008025a0067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46a069b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69"

que es de 109 bytes. Si analizamos los datos

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)

Ahora intentaré usar la biblioteca RLP para seleccionar la estructura.

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()

Quería mostrarle al código anterior que es posible elegir una estructura, pero esto aún no es cierto, porque no consideramos el tamaño de la transacción y no la firmamos. Ahora vamos a reescribir el código de trabajo.

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))

Función enviar a:

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

Aquí puede ver los módulos que faltan en el código.

¡El más importante! gasLimit será igual a 21000 para la transacción, pero no para el contrato. gasPrice necesita llegar aquí este enlace. Después de recibir gasPrice, debe convertirse de GWei a Wei

Me temo que la transacción que usas como ejemplo fue elaborada por mí. Le invitamos a compartir, pero sería bueno tener al menos una referencia adecuada, ya que la escritura se basa claramente en mi contenido aquí lsongnotes.wordpress.com/2018/01/14/…
@LinmaoSong Gracias por tu trabajo, +1
@LinmaoSong es realmente divertido que me encontré contigo aquí ya que estoy en medio de la transferencia de tu propio tutorial a JS --- Sin embargo, me estoy encontrando con un pequeño problema con la firma de ECDSA, publiqué una pregunta formal en otro hilo para que para no abarrotar este, ¿crees que podrías echarle un vistazo? ethereum.stackexchange.com/questions/83413/…
@LinmaoSong Quizás tenga razón, probablemente estudié la información en su página, pero el material presentado aquí es más completo.
Esta respuesta debe actualizarse para reflejar la última implementación, que también incluye el ID de la cadena como un parámetro RLP.
@GViz Hola. Quería saber si ha intentado enviar un contrato inteligente usando python. De acuerdo con la documentación, aumenté la cantidad de gasolina, eliminé la dirección de entrega y especifiqué el código de contrato inteligente compilado del compilador remixOnline en el parámetro 'datos'. En el explorador de blockchain, aparece un error de ejecución del contrato. Mi dirección de Ropsten es 0xa8162E5f9922661677b56Ac4c094036c173aF7E7