La estructura recursiva desencadena una excepción de JavaScript no detectada en la solidez del navegador

Estoy tratando de implementar una estructura de datos recursiva. El siguiente código no se compila en la solidez del navegador:

pragma solidity ^0.4.4;
contract TestContract {
    struct Item {
        uint someUint;
        Item[] internalItems;
    }
    Item[] items;
    function TestContract() {}
    function test() {
        Item memory item;
        items.push(item);
    }
}

Me doy cuenta de que se desaconseja la recursión en entornos de cadena de bloques, pero en este caso particular, itemestá esencialmente vacío, por lo que no esperaba que ocurriera ninguna recursión infinita. Recibo diferentes errores en Chromium y Firefox (ambos en Ubuntu 16.04).

cromo 53.0.2785.143 dice:

Uncaught JavaScript exception:
RangeError: Maximum call stack size exceeded

(y a veces incluso carga un núcleo de CPU al 100% y se congela)

Firefox 49.0.2 dice:

Uncaught JavaScript exception:
InternalError: too much recursion

A tengo dos preguntas:

  1. ¿Browser-solidity implementa completamente la especificación del compilador Solidity? Si es así, ¿cómo puede depender el mensaje de error del navegador que estoy usando? Si no, ¿cuál es la implementación del compilador de referencia?

  2. ¿Hay alguna forma de implementar estructuras de datos recursivas en Solidity? Algo así como un árbol, una lista enlazada, etc.

Problema de Github posiblemente relacionado: github.com/ethereum/browser-solidity/issues/32

Respuestas (2)

Este es un error abierto en el propio compilador de Solidity, no relacionado con la solidez del navegador o el tiempo de ejecución de Javascript: https://github.com/ethereum/solidity/issues/736

Me parece que el compilador en sí entró en una recursividad infinita y, por lo tanto, se bloqueó. La razón por la que obtiene errores diferentes es que Firefox y Chromium manejan la recursividad infinita de manera diferente.

Para citar los documentos de Solidity :

No es posible que una estructura contenga un miembro de su propio tipo, aunque la estructura misma puede ser el tipo de valor de un miembro de mapeo.

Hay una forma más sencilla de hacer estructuras de datos recursivas. Asigne a cada elemento algún tipo de identificación (ya sea un número incremental o un hash de los datos). Luego, puede almacenar solo referencias a dichas identificaciones. Por ejemplo:

struct Item {
    uint ID;
    uint someInt;
    Item[] internalItems;
};
mapping (uint => Item) public Items;
uint public nextID;
// Some time later...
uint newID = nextID++;
Items[newID] = new Item(newID, _someInt);
oldItem.internalItems.push(newID);

Este es un método algo básico. Cuanto más complicados son los elementos, más sentido tiene tener alguna función (por ejemplo, createItem()) que haga todo esto correctamente. También guardé en caché este "puntero casero" en la estructura misma en este ejemplo. Si proporciona la estructura como argumento para alguna función, de lo contrario no tendrá el uint que la identifica. Si usa un sistema hash, puede prescindir de él siempre que pueda regenerar el hash.

Además, esto le permitirá crear estructuras de datos circulares, como un gráfico cíclico.

Hay otro beneficio importante de este método. Los getters y las estructuras recursivas no se mezclan. Si llama a un captador de este tipo desde el mundo exterior, no devolverá ningún campo que sea una estructura en una estructura. Esto también se aplica a las matrices y asignaciones de longitud ilimitada dentro de las estructuras.

Si alguna vez planea que uno de estos elementos sea público, no podrá acceder fácilmente al contenido de los elementos internos. Sin embargo, Solidity felizmente devolverá una matriz de longitud fija al mundo exterior. Si está dispuesto a aceptar un número máximo de elementos internos (por ejemplo, diez), puede hacer que un dapp lea fácilmente un elemento e itere hacia abajo en el árbol.

También podría estar interesado en las bibliotecas , que le permitirían crear estructuras de datos de aspecto muy natural.

"realmente guarde en caché este "puntero casero" en la estructura misma". ¿Podría dar más detalles sobre eso, por favor?
Es posible que pueda ingresar a una situación en la que tenga acceso a la estructura, pero no al uint o hash que la identifica. Esto es especialmente posible con las bibliotecas, ya que le da a las funciones de la biblioteca punteros "reales". Dado que no puede "tomar la dirección" de la estructura en esta situación, almacenar en caché el uint o hash que apunta a ella de antemano será de gran ayuda.