区块tx解析

维护节点需要巨额的资源,ssd硬盘,很大的内存等。钱包需要交易记录,之前完全没有碰过这方面记录一下。

EOS

get_info 获取高度

1
2
3
4
5
6
7
8
{
	"server_version": "d9ad8eec",
	"head_block_num": 8592,
	"last_irreversible_block_num": 8591,
	"head_block_id": "00002190e805475db152be7d3f4f1a075efaed42827cd551b0e23c7feabbedac",
	"head_block_time": "2018-04-27T17:40:34",
	"head_block_producer": "eosio"
}

last_irreversible_block_num 就是最新的不可逆区块高度。

get_block 区块信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
    'timestamp': '2019-01-05T13:01:42.500',
    'producer': 'eosbeijingbp', 'confirmed': 0,
    'previous': '0222e1cb394fc47010985ce7f2a40d65a5333c6b620dac48c7948a3bf40315a1',
    'transaction_mroot': '4a7f7396734abcdd519046362a293a17ddd85fe9a9a3ac1d12624320241df6c7',
    'action_mroot': 'a80c78de8b736d8c12c7366aa3a799f71b8a9ac9b8532c4e3b065b315d1b4d81', 'schedule_version': 649,
    'new_producers': None,
    'header_extensions': [],
    'producer_signature': 'SIG_K1_KmQRYtEYYqAKMyi1RjQ3YasVuBpqpjyUM4eyQGrKvushRkVN7GdyfkJLZPqoskXPqj58BAVQdJN4CJeW5APBVjZZAQ5R6h',
    'transactions':[...]
    'block_extensions': [],
    'id': '0222e1cc4d5a0d80a5eb1df4c362be4e97adcf361ed1402c3724edd35e1993dd',
    'block_num': 35840460,
    'ref_block_prefix': 4095601573
  }

timestamp是区块UTC时间这非常恶心,为什么不直接给个timestamp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def dt2ts(dt, utc=False):
    if utc:
        return calendar.timegm(dt.timetuple())
    if dt.tzinfo is None:
        return int(time.mktime(dt.timetuple()))
    utc_dt = dt.astimezone(tz.tzutc()).timetuple()
    return calendar.timegm(utc_dt)


def dtstr2ts(dtstr, fmt='%Y-%m-%d %H:%M:%S', utc=False):
    dt = datetime.datetime.strptime(dtstr, fmt)
    return dt2ts(dt, utc)


def blockts(dtstr, utc=True):
    fmt = '%Y-%m-%dT%H:%M:%S.%f' if '.' in dtstr else '%Y-%m-%dT%H:%M:%S'
    return dtstr2ts(dtstr, fmt, utc)

transactions 就是需要的交易记录

内联tx

一般内联交易trx是tx_hash,可以理解为函数里面的调用其他函数,合约代码执行个人看法,看过有些内联交易可以包含很多action

普通tx

普通交易个人叫法 我也不知道叫法

种类很多,还可以自定义action。看看 EOS智能合约的源码 对比 区块浏览器 容易理解很多。

普通交易trx里面通常长这个样子

eos transfer

个人看法:

account 就是合约名=>代码中包的名字,name就是action名=>函数名,data=>函数的参数

常用的action在源码的eosio.systemeosio.token

总结

EOS 共有三大资源:CPU、NET(网络带宽)和RAM(内存)

我们知道,比特币和以太坊中的交易手续费机制,其目的就是防止大量交易使系统拥堵。而EOS取消了交易手续费, 那么如何避免系统资源的滥用?因而EOS设计了一种新的资源使用机制:根据账户中EOS的数量来分配系统资源, 包括:RAM(内存), Network Ba​​ndWidth (NET带宽) 以及CPU BandWidth (CPU 带宽)。

RAM(内存)

在EOS 中, RAM(内存)的主要特点包括:

  • 要将数据存储在区块链中需要消耗RAM,比如在EOS 中转账、购买资源、投票等操作的时候,都有可能会消耗RAM (内存)。
  • 如果你的RAM 消耗殆尽,那么你是无法进行上述这些需要消耗RAM的操作的,所以我们需要有足够的RAM。
  • 通过购买获得的EOS RAM 资源可以买卖,买卖的价格根据市场行情动态调节,这个特点与买卖EOS一样。
  • RAM可以通过EOS购买的方式获得也可以通过好友帮你购买,这个特点和通过抵押方式获取CPU 资源以及NET 资源不太一样。
  • 用户在买卖RAM 资源的时候,各需要消耗0.5 % (千分之五) 的手续费,总共是1% 的手续费。这笔费用被存在eosio.ramfee 中,由BP 节点进行管理。
  • 内存是消耗资源,不可赎回,只能买卖。
  • RAM本质上是为智能合约中调用的每个交易提供资源的gas。

NET带宽与CPU带宽

在EOS中,NET带宽与CPU带宽的特性差不多,它们的主要特点包括:

  • 它们采用抵押EOS的方式获取。当不再需要CPU与带宽时,抵押的EOS通证可以赎回,在赎回的时候,存在三天的赎回期。
  • 如果你持有全网1%的EOS,那就可以抵押这些EOS来获得全网1%的CPU和带宽。这样就可以隔离开所有的DAPP,防止资源竞争和恶意的DDOS供给,无论其他的DAPP如何拥堵, 你自己的带宽都不受影响。
  • 每次使用转账功能时,都会消耗网络带宽资源。
  • 网络带宽取决于过去三天消费的平均值,作为你下一次执行操作的费率。
  • 如果没有足够的网络带宽资源的话,你是无法使用EOS 网络转账等基本功能的。
  • 带宽资源是可以随着时间的推移,自动释放。
  • NET带宽用于增加算力,CPU带宽增加网络资源

ETH

eth_blockNumber 获取高度

返回就是最新高度,这意味着eth_blockNumber-30个确认

eth_getBlockByNumber 区块信息

主要解析transactions中内容

1
2
3
4
5
6
7
.....
from: 20 Bytes - address of the sender.
to: 20 Bytes - address of the receiver. null when its a contract creation transaction.
value: value transferred in Wei.
gasPrice: gas price provided by the sender in Wei.
gas: gas provided by the sender.
input: the data send along with the transaction.

这玩意只有ETH的transfer,token没有。对于手续费更是一无所知。 这些都需要绕一个弯刚接触的我懵逼了好久

eth_getTransactionReceipt

用于确认tx执行情况

1
2
3
4
5
6
7
....
from: 20 Bytes - address of the sender.
to: 20 Bytes - address of the receiver. Null when the transaction is a contract creation transaction.
gasUsed: the amount of gas used by this specific transaction alone.
contractAddress: 20 Bytes - the contract address created, if the transaction was a contract creation, otherwise - null.
logs: Array - Array of log objects, which this transaction generated.
status: either 1 (success) or 0 (failure)

status最终确认tx是否完成。

gas

这涉及到gas理解

  • tx的执行依靠消耗gas支付给矿工,无论成功与否都要收取手续费。
  • gasPrice 用户愿意花费于每个 Gas 单位的价钱, gas => Gas limit用户愿意为执行某个操作或确认交易支付的最大Gas量,fee = gasUsed * gasPrice手续费, 用不完会退还给用户。
  • Gas Price越高,交易优先级越高,打包交易速度越快。消耗的gas > Gas limit则tx失败

单位处理 一般都是区块中用最小单位WEI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# web3 api
'gas_price' = Web3.fromWei(raw_trx['gasPrice'], 'ether')


units = {
    'wei':          decimal.Decimal('1'),  # noqa: E241
    'kwei':         decimal.Decimal('1000'),  # noqa: E241
    'babbage':      decimal.Decimal('1000'),  # noqa: E241
    'femtoether':   decimal.Decimal('1000'),  # noqa: E241
    'mwei':         decimal.Decimal('1000000'),  # noqa: E241
    'lovelace':     decimal.Decimal('1000000'),  # noqa: E241
    'picoether':    decimal.Decimal('1000000'),  # noqa: E241
    'gwei':         decimal.Decimal('1000000000'),  # noqa: E241
    'shannon':      decimal.Decimal('1000000000'),  # noqa: E241
    'nanoether':    decimal.Decimal('1000000000'),  # noqa: E241
    'nano':         decimal.Decimal('1000000000'),  # noqa: E241
    'szabo':        decimal.Decimal('1000000000000'),  # noqa: E241
    'microether':   decimal.Decimal('1000000000000'),  # noqa: E241
    'micro':        decimal.Decimal('1000000000000'),  # noqa: E241
    'finney':       decimal.Decimal('1000000000000000'),  # noqa: E241
    'milliether':   decimal.Decimal('1000000000000000'),  # noqa: E241
    'milli':        decimal.Decimal('1000000000000000'),  # noqa: E241
    'ether':        decimal.Decimal('1000000000000000000'),  # noqa: E241
    'kether':       decimal.Decimal('1000000000000000000000'),  # noqa: E241
    'grand':        decimal.Decimal('1000000000000000000000'),  # noqa: E241
    'mether':       decimal.Decimal('1000000000000000000000000'),  # noqa: E241
    'gether':       decimal.Decimal('1000000000000000000000000000'),  # noqa: E241
    'tether':       decimal.Decimal('1000000000000000000000000000000'),  # noqa: E241
}

token transfer

不要尝试解析 input data,这是不准的,有可能会坑。

例如0x8ea17abad8c19cf910dcb97a060b1a74d461426c6c8dd8d7bbefc76ee3e84d9b,最好的方法是解析logs。

解析logs 每一个log就相当于EOS里面的action,也是多种多样的。

1
2
3
4
5
....
address: 20 Bytes - address from which this log originated.
data: contains one or more 32 Bytes non-indexed arguments of the log.
topics: Array of 0 to 4 32 Bytes of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
....

注意力放在topics数组,通常正常的transfer,topic[0]是函数名,其他的是函数参数。

token transfer的函数名

1
TOKEN_TRANSFER_EVENT_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'

这玩意是怎么来的

1
2
3
4
5
def test3():
    t = 'Transfer(address,address,uint256)'
    print(Web3.sha3(text=t).hex())

'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
  • log.address => token_address

我们仍然不知道token的symbol是什么?

1
2
3
4
5
6
7
8
9
10
11
contract ERC20Interface {
    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

ETH合约编译后产生ABI,ABI就是告诉我们合约怎样解析,如果没有严格遵循ERC20的规则,就可能解析不出来。字符集必须使用utf8mb4

解析token需要获取symboldecimalsdecimals是小数点偏移的位数

1
2
3
4
5
6
7
8
9
10
11

ERC20_ABI = json.loads('''
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]
''')

checksum_address = self._web3.toChecksumAddress(token_address)
contract = self._web3.eth.contract(address=checksum_address, abi=eth_config.ERC20_ABI)
# symbol 入库用utf8mb4 utf8mb4是utf8的超集 支持更多的特殊符号
symbol = self._call_contract_function(contract.functions.symbol())
decimals = self._call_contract_function(contract.functions.decimals())

通过 etherscan.io 查看BNB read contract

通常topic有三项

  • topic[0] => 函数名 TOKEN_TRANSFER_EVENT_TOPIC
  • topic[1] => from 发送人 uint256
  • topic[2] => to 接收者 uint256
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# uint256 还在前面补0 实质上获取后40位就可以了
def address(addr_str, eth_addr_len=eth_cfg.ETH_ADDRESS_LEN):
    addr_str = addr_str.strip()
    if addr_str is None:
        return None
    if addr_str and len(addr_str) > eth_addr_len:
        return ('0x' + addr_str[-eth_addr_len:]).lower()
    return normalized_address(addr_str)


def normalized_address(address):
    if address is None or not isinstance(address, str):
        return address
    return address.lower().strip()
  • log.data => quantity 发送数量 token_value( hex_to_int , token获得的 decimals )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def hex_to_int(hex_string):
    if hex_string is None:
        return None
    if hex_string == '0x':
        return 0
    return int(hex_string, 16)

# 单位转化
def token_value(int_value, decimals):
    if decimals is None:
        return decimal.Decimal(int_value)

    unit = decimal.Decimal(10 ** decimals)
    with localcontext() as ctx:
        ctx.prec = 999
        d = decimal.Decimal(value=int_value, context=ctx)
        result_value = d / unit

    return result_value

总结

  • 智能合约传上以太坊之后,它就变得不可更改, 这种永固性意味着你的代码永远不能被调整或更新。完全不用担心函数被人篡改而得到意外的结果。
  • 可以留个后门修改DAPP的关键部分,同时声明合约函数的所有权,不然后门被人随便用。所以最好阅读并理解它的源代码,才能防止其中没有被部署者恶意植入后门,也可以找到别人的漏洞为所欲为。
  • 合约中有些不要gas的view函数,存储操作非常多的gas

在 ETH 中有两种类型的账户:

  • 一种是被私钥控制的账户,它没有任何的代码.
  • 合约代码控制的账户,能够在每一次收到消息时,执行保存在 contract_code 中的代码,所有的合约在网络中都能够响应其他账户的请求和消息并提供一些服务。

账户余额模型 每一个账户都包含四个字段 (nonce, ether_balance, contact_code, storage) 所有账户的 nonce 都必须从 0 开始递增,当前账户每使用 nonce 签发并广播一笔交易之后,都会将其 +1,来解决重放攻击的问题。

BTC

getblockhash & getblock

height -> getblockhash -> block_hash -> getblock

解析出来 只有tx_id数组

1
2
3
...
tx:区块内所有交易组成的数组,成员为交易id
...

getrawtransaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    。。。。
    "vin": [
        {
            "txid": "2ac0daff49a4ff82a35a4864797f99f23c396b0529c5ba1e04b3d7b97521feba",
            "vout": 0,
            "scriptSig": {
                "asm": "3044022013d212c22f0b46bb33106d148493b9a9723adb2c3dd3a3ebe3a9c9e3b95d8cb00220461661710202fbab550f973068af45c294667fc4dc526627a7463eb23ab39e9b[ALL] 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
                "hex": "473044022013d212c22f0b46bb33106d148493b9a9723adb2c3dd3a3ebe3a9c9e3b95d8cb00220461661710202fbab550f973068af45c294667fc4dc526627a7463eb23ab39e9b01410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
            },
            "sequence": 4294967295
        }
    ],
    "vout": [
        {
            "value": 0.06990000,
            "n": 0,
            "scriptPubKey": {
            "asm": "OP_DUP OP_HASH160 01b81d5fa1e55e069e3cc2db9c19e2e80358f306 OP_EQUALVERIFY OP_CHECKSIG",
                "hex": "76a91401b81d5fa1e55e069e3cc2db9c19e2e80358f30688ac",
                "reqSigs": 1,
                "type": "pubkeyhash",
                "addresses": [
                    "1A6Ei5cRfDJ8jjhwxfzLJph8B9ZEthR9Z"
                ]
            }
        }
    ]
}

UXTO

Unspent Transaction output 未被使用的交易输出。

把UXTO想象成一张纸币,是不可分割的整体,使用时存在着找零。账户中的余额并非是一个数字, 而是由当前区块链网络中所有跟当前账户有关的UTXO组成的。

基于 UTXO 模型的tx由输入和输出两个部分组成。

1
2
3
4

sum(inputs.value) = sum(outputs.value) + fee
sum(outputs.value) = 需要转账的数量 + 找零

vin里面txid,vout说明了in的来源。换句话说 UXTO就是较早已确认区块的未作为in的out

左图的vin中txid=’5f01378fae317945e37200f0445aaf160271c506a48d465c09c8b814ba84b3c8’ vout=2 如此类推,就好像一条链一样,有因才有果,可以防止同一个UXTO被使用两次。

由于解析有点复杂,因为每个in都不知道对应的地址和数量, 必须通过请求一次getrawtransaction才知道答案,而BTC的tx有点多都是几千,一个tx有若干个in(曾经试过一个tx900+in),从主网节点拿数据,解析很慢。 除非从创世块开始保存。

由于结构比较怪异类似于多对多的结构,所以分开in、out两个表用tx_hash连接