Llamadas de contrato constantes de web3 que devuelven valores inconsistentes

Tengo este dapp en la red principal aquí .

El siguiente contrato se ha desplegado en esta dirección :

pragma solidity ^0.4.19;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'zeppelin-solidity/contracts/lifecycle/Destructible.sol';
import 'zeppelin-solidity/contracts/lifecycle/Pausable.sol';
import 'zeppelin-solidity/contracts/math/SafeMath.sol';

contract CryptoTwittos is Ownable, Pausable, Destructible {
  using SafeMath for uint;

  // A Twitto is owned by a stealer and has a price
  struct Twitto {
    address stealer;
    uint price;
  }

  // Look up Twitto by ids
  mapping(uint => Twitto) public twittos;

  // All Twitto ids and counter
  uint[] public twittoIds;
  uint public twittosCounter;


  // Fire event when steal happens
  event stealEvent(
    uint indexed id,
    address indexed owner,
    uint price,
    address indexed stealer,
    uint newPrice
  );


  // Get twittoIds
  function getTwittoIds(bool all) public view returns (uint[]) {
    // Return empty array if counter is zero
    if (twittosCounter == 0) return new uint[](0);

    if (all) {
      // Return all of them
      return twittoIds;

    } else {
      // Create memory array to store filtered ids
      uint[] memory filteredIds = new uint[](twittosCounter);
      // Store number of belongings
      uint twittosCount = 0;

      for (uint i = 0; i < twittosCounter; i++) {
        // Check if stealer is sender
        if (twittos[twittoIds[i]].stealer == msg.sender) {
          filteredIds[twittosCount] = twittoIds[i];
          twittosCount++;
        }
      }

      // Copy the filteredIds array into a smaller array
      uint[] memory trophies = new uint[](twittosCount);
      for (uint j = 0; j < twittosCount; j++) {
        trophies[j] = filteredIds[j];
      }
      return trophies;
    }
  }

  // Steal a Twitto by paying its price and setting a new one
  function steal(uint id, uint256 newPrice) payable whenNotPaused public {

    // look up the twitto and put on storage
    Twitto storage _twitto = twittos[id];

    // Prevent self stealing!
    require(msg.sender != _twitto.stealer);

    // Make sure the sender pays the right price
    require(msg.value == _twitto.price);

    // Make sure that the new price is higher than the old price
    require(newPrice > _twitto.price);

    // Transfer value with the 1% dev fee
    if (msg.value > 0) {
      _twitto.stealer.transfer(msg.value.mul(99).div(100));
    }

    // Push new Twitto if not existing
    if (_twitto.price == 0) {
      twittoIds.push(id);
      twittosCounter++;
    }

    // Trigger event
    stealEvent(id, _twitto.stealer, _twitto.price, msg.sender, newPrice);

    // Store new stealer
    _twitto.stealer = msg.sender;

    // Store new price
    _twitto.price = newPrice;

  }

  function withdraw() public onlyOwner {

    // Transfer balance to owner
    msg.sender.transfer(address(this).balance);
  }


}

El método constante que está causando problemas es getTwittoIds: todo funcionaba bien, pero luego el tamaño de las matrices creció un poco (como puede ver con la cantidad de transacciones en Etherscan ) y ahora esta llamada de método constante a veces devuelve una matriz vacía y a veces no.

Al leer preguntas como esta , pensé que esto se debía a que el límite de gas era demasiado bajo (para llamadas constantes), pero el problema sigue ocurriendo.

Los pasos más fáciles para reproducir este "error" son los siguientes:

  1. Vaya a https://cryptotwittos.com/
  2. Asegúrate de que MetaMask esté habilitado
  3. Abrir consola
  4. Obtener contrato con abi:

    var CT = web3.eth.contract([{"constant":true,"inputs":[],"name":"twittosCounter","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"destroy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"twittos","outputs":[{"name":"stealer","type":"address"},{"name":"price","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"twittoIds","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"}],"name":"destroyAndSend","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":true,"name":"stealer","type":"address"},{"indexed":false,"name":"newPrice","type":"uint256"}],"name":"stealEvent","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"constant":true,"inputs":[{"name":"all","type":"bool"}],"name":"getTwittoIds","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"newPrice","type":"uint256"}],"name":"steal","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}])
    
  5. Obtener Instancia

    var CTInstance = CT.at("0xa4b9054417ee4f06453152a45ebeba7786c84c66")
    
  6. Método de llamada que debería devolver todos los TwittoIds

    CTInstance.getTwittoIds(true, console.log)
    
  7. Convénzase de que el método no debe devolver una matriz vacía

    CTInstance.twittosCounter(console.log)
    

Después de dos días de investigación todavía no tengo ni idea de lo que está pasando , por lo que cualquier ayuda sería muy apreciada.

¡Muchas gracias!

¿Cuáles son los casos en los que devuelve una matriz no vacía?
@ElishaDrion CTInstance.getTwittoIds(true, console.log)a veces devuelve una matriz no vacía. También intenté configurar el gas de esta CTInstance.getTwittoIds(true, {gas: 990000000}, console.log)manera, pero sigue siendo "aleatorio" (a veces está vacío, a veces no)
Pero, ¿cuándo devuelve una matriz no vacía? ¿En qué condiciones? ¿Cuál es el tamaño de la matriz cuando lo hace? etc... Además, tenga en cuenta que la lectura no es inmediata, y cuanto mayor sea su conjunto, más tiempo llevará devolver algo.
Las condiciones es lo que estoy tratando de averiguar. Básicamente, ejecuto el mismo comando en la consola repetidamente y obtengo aleatoriamente una matriz vacía o la matriz adecuada (~ 1200 entradas). Estoy pasando una devolución de llamada, mis lecturas son todas asíncronas.

Respuestas (3)

Intenté ejecutar esto en un gethnodo y de hecho regresa0x

Sin embargo, ejecutar esto en un nodo de paridad devolvió un gran resultado (supongo que esto es lo que necesita)

> eth.call({to:"0xa4b9054417ee4f06453152a45ebeba7786c84c66", data:"0xd15e656c0000000000000000000000000000000000000000000000000000000000000001", gas: "440000"})

Salida: https://gist.github.com/cleanunicorn/b90fc5141d157e7dcc861522af25cb5a

El gas consumido para esta operación está cerca 440000y aumentará.

Todavía no he comprobado el código fuente de geth, pero parece que hay un límite de cuánto puede ejecutarse antes de que se detenga.

Infura tiene gethnodos y por eso (si los estás usando) devuelve 0x. Posiblemente también estén experimentando con paritycompilaciones personalizadas y, a veces, obtienes el resultado correcto.

¡Interesante! ¿Conoce alguna forma de aumentar este límite de gas para que gethse comporte como parity? Entiendo que la matriz es grande pero no tan grande...
Probé la misma llamada en geth (gas 400,000) pero no obtuve respuesta, incluso probé con 1,000,000 de gas pero geth se niega a procesar tantos datos. Si realmente desea usar geth, debe bifurcarlo y eliminar el límite. De lo contrario, deberías usar la paridad.

MetaMask usa nodos Infura por defecto. Intenté llamar a su contrato con JSON RPC y obtuve 0xel resultado.

Mi mejor suposición es que Infura impone limitaciones sobre lo que se puede ejecutar en el cliente EVM con eth_call, y al alcanzar ese límite devuelve 0x en lugar de un error más descriptivo. Para confirmar esto, necesitaría sincronizar mi nodo local con la red principal y ver si la API se comporta como se esperaba.

Problemas relacionados: https://github.com/INFURA/infura/issues/70 , https://github.com/ethereum/web3.py/issues/607

El mismo problema para mí cuando uso web3.eth.contractcon el proveedor MetaMask .

Resuelvo el problema usando web3.eth.contractdespués de redefinir web3 con el proveedor Infura .

De esta manera, todo funciona bien y las llamadas a funciones nunca regresan 0x.

Espero que esto pueda ayudar.