您正在查看: Ethereum 分类下的文章

以太坊合约中是否可以获取交易hash?

答案:不能
txhash 是keccak256(signedTransaction).
此 keccak256 函数可用作 Solidity 函数 http://solidity.readthedocs.io/en/v0.4.21/units-and-global-variables.html

所以你需要构造,signedTransaction因为这个值没有暴露于可靠性,参见。https://stackoverflow.com/questions/49803424/how-can-we-access-rlp-encoded-signed-raw-transaction-in-solidity

signedTransaction 需要的参数是

  1. nonce
  2. gas price
  3. gas limit
  4. to
  5. value in wei
  6. data
  7. ecdsaV
  8. ecdsaR
  9. ecdsaS

值 3 不是直接可用的,但您可以在代码执行的任何时候获取当前剩余的 gas,并从中计算出执行开始后可用的气体量。值 1、7、8 和 9(nonce 和签名值)不能使用solidity,也不能使用汇编代码(可以在solidity 源代码文件中内联编写)。所以很遗憾该问题无法解决。

https://ethereum.stackexchange.com/questions/45648/how-to-calculate-the-assigned-txhash-of-a-transaction
https://github.com/ethereum/EIPs/issues/901

以太坊索引器通过 ETH 地址获取交易列表

https://github.com/Adamant-im/ETH-transactions-storage/blob/master/ethsync.py

# Indexer for Ethereum to get transaction list by ETH address
# https://github.com/Adamant-im/ETH-transactions-storage
# 2021 ADAMANT Foundation (devs@adamant.im), Francesco Bonanno (mibofra@parrotsec.org),
# Guénolé de Cadoudal (guenoledc@yahoo.fr), Drew Wells (drew.wells00@gmail.com)
# 2020-2021 ADAMANT Foundation (devs@adamant.im): Aleksei Lebedev
# 2017-2020 ADAMANT TECH LABS LP (pr@adamant.im): Artem Brunov, Aleksei Lebedev
# v2.0

from os import environ
from web3 import Web3
from web3.middleware import geth_poa_middleware
import psycopg2
import time
import sys
import logging
#from systemd.journal import JournalHandler

# Get env variables or set to default
dbname = environ.get("DB_NAME")
startBlock = environ.get("START_BLOCK") or "1"
confirmationBlocks = environ.get("CONFIRMATIONS_BLOCK") or "0"
nodeUrl = environ.get("ETH_URL")
pollingPeriod = environ.get("PERIOD") or "20"

if dbname == None:
    print('Add postgre database in env var DB_NAME')
    exit(2)

if nodeUrl == None:
    print('Add eth url in env var ETH_URL')
    exit(2)

# Connect to Ethereum node
if nodeUrl.startswith("http"):
    web3 = Web3(Web3.HTTPProvider(nodeUrl))
if nodeUrl.startswith("ws"):
    web3 = Web3(Web3.WebsocketProvider(nodeUrl)) # "ws://publicnode:8546"
if web3 == None:
    web3 = Web3(Web3.IPCProvider(nodeUrl)) # "/home/parity/.local/share/openethereum/jsonrpc.ipc"

web3.middleware_onion.inject(geth_poa_middleware, layer=0)

# Start logger
#logger = logging.getLogger("EthIndexerLog")
logger = logging.getLogger("eth-sync")
logger.setLevel(logging.INFO)

# File logger
#lfh = logging.FileHandler("/var/log/ethindexer.log")
lfh = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
lfh.setFormatter(formatter)
logger.addHandler(lfh)

# Systemd logger, if we want to user journalctl logs
# Install systemd-python and 
# decomment "#from systemd.journal import JournalHandler" up
#ljc = JournalHandler()
#formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
#ljc.setFormatter(formatter)
#logger.addHandler(ljc)

try:
    logger.info("Trying to connect to "+ dbname)
    conn = psycopg2.connect(dbname)
    conn.autocommit = True
    logger.info("Connected to the database")
except:
    logger.error("Unable to connect to database")
    exit(1)

# Delete last block as it may be not imparted in full
cur = conn.cursor()
cur.execute('DELETE FROM public.ethtxs WHERE block = (SELECT Max(block) from public.ethtxs)')
cur.close()
conn.close()

# Wait for the node to be in sync before indexing
logger.info("Waiting Ethereum node to be in sync...")

while web3.eth.syncing != False:
    # Change with the time, in second, do you want to wait
    # before cheking again, default is 5 minutes
    time.sleep(300)

logger.info("Ethereum node is synced!")

# Adds all transactions from Ethereum block
def insertion(blockid, tr):
    time = web3.eth.getBlock(blockid)['timestamp']
    for x in range(0, tr):
        trans = web3.eth.getTransactionByBlock(blockid, x)
        # Save also transaction status, should be null if pre byzantium blocks
        status = bool(web3.eth.get_transaction_receipt(trans['hash']).status)
        txhash = trans['hash'].hex()
        value = trans['value']
        inputinfo = trans['input']
        # Check if transaction is a contract transfer
        if (value == 0 and not inputinfo.startswith('0xa9059cbb')):
            continue
        fr = trans['from']
        to = trans['to']
        gasprice = trans['gasPrice']
        gas = web3.eth.getTransactionReceipt(trans['hash'])['gasUsed']
        contract_to = ''
        contract_value = ''
        # Check if transaction is a contract transfer
        if inputinfo.startswith('0xa9059cbb'):
            contract_to = inputinfo[10:-64]
            contract_value = inputinfo[74:]
        # Correct contract transfer transaction represents '0x' + 4 bytes 'a9059cbb' + 32 bytes (64 chars) for contract address and 32 bytes for its value
        # Some buggy txs can break up Indexer, so we'll filter it
        if len(contract_to) > 128:
            logger.info('Skipping ' + str(txhash) + ' tx. Incorrect contract_to length: ' + str(len(contract_to)))
            contract_to = ''
            contract_value = ''
        cur.execute(
            'INSERT INTO public.ethtxs(time, txfrom, txto, value, gas, gasprice, block, txhash, contract_to, contract_value, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
            (time, fr, to, value, gas, gasprice, blockid, txhash, contract_to, contract_value, status))

# Fetch all of new (not in index) Ethereum blocks and add transactions to index
while True:
    try:
        conn = psycopg2.connect(dbname)
        conn.autocommit = True
    except:
        logger.error("Unable to connect to database")

    cur = conn.cursor()

    cur.execute('SELECT Max(block) from public.ethtxs')
    maxblockindb = cur.fetchone()[0]
    # On first start, we index transactions from a block number you indicate
    if maxblockindb is None:
        maxblockindb = int(startBlock)

    endblock = int(web3.eth.blockNumber) - int(confirmationBlocks)

    logger.info('Current best block in index: ' + str(maxblockindb) + '; in Ethereum chain: ' + str(endblock))

    for block in range(maxblockindb + 1, endblock):
        transactions = web3.eth.getBlockTransactionCount(block)
        if transactions > 0:
            insertion(block, transactions)
        else:
            logger.debug('Block ' + str(block) + ' does not contain transactions')
    cur.close()
    conn.close()
    time.sleep(int(pollingPeriod))

一种高效的以太币和代币余额扫描方案

开源地址:https://github.com/MyCryptoHQ/eth-scan
智能合约:

// SPDX-License-Identifier: MIT

pragma solidity 0.8.3;

/**
 * @title An Ether or token balance scanner
 * @author Maarten Zuidhoorn
 * @author Luit Hollander
 */
contract BalanceScanner {
  struct Result {
    bool success;
    bytes data;
  }

  /**
   * @notice Get the Ether balance for all addresses specified
   * @param addresses The addresses to get the Ether balance for
   * @return results The Ether balance for all addresses in the same order as specified
   */
  function etherBalances(address[] calldata addresses) external view returns (Result[] memory results) {
    results = new Result[](addresses.length);

    for (uint256 i = 0; i < addresses.length; i++) {
      results[i] = Result(true, abi.encode(addresses[i].balance));
    }
  }

  /**
   * @notice Get the ERC-20 token balance of `token` for all addresses specified
   * @dev This does not check if the `token` address specified is actually an ERC-20 token
   * @param addresses The addresses to get the token balance for
   * @param token The address of the ERC-20 token contract
   * @return results The token balance for all addresses in the same order as specified
   */
  function tokenBalances(address[] calldata addresses, address token) external view returns (Result[] memory results) {
    results = new Result[](addresses.length);

    for (uint256 i = 0; i < addresses.length; i++) {
      bytes memory data = abi.encodeWithSignature("balanceOf(address)", addresses[i]);
      results[i] = staticCall(token, data, 20000);
    }
  }

  /**
   * @notice Get the ERC-20 token balance from multiple contracts for a single owner
   * @param owner The address of the token owner
   * @param contracts The addresses of the ERC-20 token contracts
   * @return results The token balances in the same order as the addresses specified
   */
  function tokensBalance(address owner, address[] calldata contracts) external view returns (Result[] memory results) {
    results = new Result[](contracts.length);

    bytes memory data = abi.encodeWithSignature("balanceOf(address)", owner);
    for (uint256 i = 0; i < contracts.length; i++) {
      results[i] = staticCall(contracts[i], data, 20000);
    }
  }

  /**
   * @notice Call multiple contracts with the provided arbitrary data
   * @param contracts The contracts to call
   * @param data The data to call the contracts with
   * @return results The raw result of the contract calls
   */
  function call(address[] calldata contracts, bytes[] calldata data) external view returns (Result[] memory results) {
    return call(contracts, data, gasleft());
  }

  /**
   * @notice Call multiple contracts with the provided arbitrary data
   * @param contracts The contracts to call
   * @param data The data to call the contracts with
   * @param gas The amount of gas to call the contracts with
   * @return results The raw result of the contract calls
   */
  function call(
    address[] calldata contracts,
    bytes[] calldata data,
    uint256 gas
  ) public view returns (Result[] memory results) {
    require(contracts.length == data.length, "Length must be equal");
    results = new Result[](contracts.length);

    for (uint256 i = 0; i < contracts.length; i++) {
      results[i] = staticCall(contracts[i], data[i], gas);
    }
  }

  /**
   * @notice Static call a contract with the provided data
   * @param target The address of the contract to call
   * @param data The data to call the contract with
   * @param gas The amount of gas to forward to the call
   * @return result The result of the contract call
   */
  function staticCall(
    address target,
    bytes memory data,
    uint256 gas
  ) private view returns (Result memory) {
    uint256 size = codeSize(target);

    if (size > 0) {
      (bool success, bytes memory result) = target.staticcall{ gas: gas }(data);
      if (success) {
        return Result(success, result);
      }
    }

    return Result(false, "");
  }

  /**
   * @notice Get code size of address
   * @param _address The address to get code size from
   * @return size Unsigned 256-bits integer
   */
  function codeSize(address _address) private view returns (uint256 size) {
    // solhint-disable-next-line no-inline-assembly
    assembly {
      size := extcodesize(_address)
    }
  }
}

测试例子
https://github.com/MyCryptoHQ/eth-scan/blob/master/tests/BalanceScanner.test.ts

为什么LINK使用ERC-677标准发行token

等等,LINK不是ERC20吗,怎么又成了ERC677了?
别急,我们先从ERC20开始说起。

ERC20是一套协议标准,代码角度来说就是一套接口API。在这个协议标准下,只要实现了协议标准所规定的方法,都可以作为ERC20代币的实现。协议规定必须实现的方法有:

// 1. 代币发行总量
function totalSupply() public view returns (uint256)

// 2. _owner账户的代币余额
function balanceOf(address _owner) public view returns (uint256 balance)

// 3. 转移_value数量的代币到_to地址
function transfer(address _to, uint256 _value) public returns (bool success)

// 4. 从_address地址转移_value数量的代币到_to地址
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

// 5. 允许_spender可以提取总额为_value数量的代币,提取不限次数
function approve(address _spender, uint256 _value) public returns (bool success)

// 6. 返回_spender还可以从_owner提取的代币数量
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

除了这几个方法,ERC20还规定了两个事件:

// 当成功转移token时,触发Transfer事件,记录转账的发送方、接收方和转账金额
event Transfer(address indexed _from, address indexed _to, uint256 _value)

// 当调用approval函数成功时,触发Approval事件,记录批准的所有方、获取方和批准金额
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

以上基本上就是ERC20代币的全部内容了。由于LINK代币不仅仅是代币,还承担了链上与链下数据传递的功能,所以如果使用ERC20代币的标准无法满足这个需求,于是LINK选择了ERC677协议标准来实现。

ERC677标准是ERC20的一个扩展,它继承了ERC20的所有方法和事件,由Chainlink的CTO Steve Ellis首次提出。ERC677除了包含了ERC20的所有方法和事件之外,增加了一个transferAndCall 方法:

function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success)

接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。这就给了智能合约的应用场景有了很大的想象空间。比如LINK的token合约就是一个ERC677合约,而Chainlink的Oracle合约,是一个可以接收ERC677的合约,它含有onTokenTransfer方法,可以在收到LINK的转账的时候执行预言机相关的业务逻辑。

LINK token contract:

...

  /**
  * @dev 转移token到合约地址,并携带额外数据
  * @param _to 转到的地址
  * @param _value 转账金额
  * @param _data 传递给接受合约的额外数据
  */
  function transferAndCall(address _to, uint _value, bytes _data)
    public
    returns (bool success)
  {
    super.transfer(_to, _value);
    Transfer(msg.sender, _to, _value, _data);
    if (isContract(_to)) {
      contractFallback(_to, _value, _data);
    }
    return true;
  }

...

Oracle 合约:

...

  /**
    * @notice 在LINK通过`transferAndCall`方法发送到合约时被调用
    * @dev 负载数据的前两个字节会被`_sender`和 `_amount`的值覆盖来保证正确性。并会调用oracleRequest方法
    * @param _sender 发送方地址
    * @param _amount 发送的LINK数量(单位是wei)
    * @param _data 交易的负载数据
    */
  function onTokenTransfer(
    address _sender,
    uint256 _amount,
    bytes _data
  )
    public
    onlyLINK
    validRequestLength(_data)
    permittedFunctionsForLINK(_data)
  {
    assembly {
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 36), _sender) // ensure correct sender is passed
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 68), _amount)    // ensure correct amount is passed
    }
    // solium-disable-next-line security/no-low-level-calls
    require(address(this).delegatecall(_data), "Unable to create request"); // calls oracleRequest
  }

...

总结:

LINK代币合约是ERC677合约,它是ERC20合约的一个扩展,兼容ERC20协议标准。它可以在转账时携带数据,并触发接收合约的业务逻辑,这一特点可以帮助智能合约扩大应用场景。

参考

https://eips.ethereum.org/EIPS/eip-20
http://blockchainers.org/index.php/2018/02/08/token-erc-comparison-for-fungible-tokens/
https://github.com/ethereum/EIPs/issues/677
https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code
https://etherscan.io/address/0x64fe692be4b42f4ac9d4617ab824e088350c11c2#code

转载自:https://learnblockchain.cn/article/588

block.Hash()计算得到的hash 与 eth_getBlockByNumber获取到的值不一致

1.10.8版本出现的一个bug

rpc returndata 使用一个字段miner,但不包含coinbase. 因此,如果调用者想要在远程端计算头/块哈希,这是不可能的,因为我们不再提供正确的值coinbase。

通过revert提交解决
https://github.com/ethereum/go-ethereum/pull/23466/files

等待 1.10.9解决,或者使用master分支,文章此时已合入

SHA-1: 62ad17fb0046243255048fbf8cb0882f48d8d850

* Revert "eth, internal/ethapi: make RPC block miner field show block sealer correctly (#23312)" (#23466)

https://github.com/ethereum/go-ethereum/issues/23523
https://github.com/ethereum/go-ethereum/issues/23463
https://github.com/ethereum/go-ethereum/issues/23512
https://github.com/ethereum/go-ethereum/pull/23466