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:
- This is only the logic structure. The actually data is encoded in
RLP format, thus is longer due to the added length prefix.
- 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