Mapping vs Arrays: duplicación de consumo de gas de variables

Tengo un contrato que tiene una estructura que incluye la dirección y la unidad, y el mapeo de una estructura, como esta.

  struct User {
    uint count;
    address userAddress;
  }
  mapping (address => User) users;

Hay un punto en el que necesito hacer una copia de este mapeo y borrar las entradas del antiguo, como este pseudocódigo:

  users_backup = users;
  users = [];

Por supuesto, este código no funcionará, así que aprendí que puedes recorrerlo así:

Asignación de mapeo en Solidity

Pero eso parece caro, especialmente después de leer:

Copiar una asignación del contrato A al contrato B

Entonces, ¿cómo hago esto de manera eficiente?

Con un mapeo de 100k usuarios o 1 millón o incluso 100 millones de entradas, ¿sería factible este método de bucle? ¿O sería mejor cambiar mi arquitectura?

uint[] userCounts;
address[] userAddresses;

Podría usar una serie de matrices en lugar de mapas. Entonces, ¿podría copiar una matriz a una nueva variable y borrar la variable anterior en una o dos líneas con menos gas? Lo difícil es que incluso los arreglos tendrán que ser arreglos variables porque no puedo saber de antemano cuántos usuarios lo usarán.

¿Qué harías para minimizar el consumo de gas? No estoy seguro de cuál es el mejor patrón, ¡gracias!

Estoy seguro de que alguien puede resolverlo, pero tenemos que encontrar un nuevo enfoque para "Hay un punto en el que necesito hacer una copia de este mapeo". No hay una buena manera de hacer una reorganización a gran escala. ¿Qué necesitamos lograr?
@RobHitchens ese es un buen punto. He pensado en un enfoque alternativo que probablemente sea el correcto, pero no estoy muy seguro de cómo hacerlo. Posiblemente podría resolver el problema instanciando una nueva versión del contrato, ya que esencialmente estoy tratando de restaurar la mayor parte del estado a su posición original de 'constructor' (con la variable adicional agregada) users_backup. pero soy nuevo en Solidity, por lo que parecía más complejo que simplemente restablecer todas las variables en el mismo contrato...
Bueno, los patrones son bastante diferentes. ¿Por qué crees que necesitas hacer algo de eso? ¿Puede describir un requisito sin recurrir a los detalles de implementación? Es probable que el método para lograr algo de manera eficiente sea bastante diferente de las intuiciones sobre cómo comenzar.
@RobHitchens los requisitos sin detalles de implementación son simplemente que el contrato es cíclico: realiza un ciclo anual de modo que para hacer los cálculos durante el próximo año solo se requieren algunos de los datos del año anterior, pero también debemos generar/registrar nuevos datos durante el año en curso. Este requisito cíclico es la razón por la que pensé en copiar una variable sobre otra y, en consecuencia, produje la solución de bucle de mapeo que presenté anteriormente. en pocas palabras, ¿hay una mejor manera?
There is a point at which I need to make a copy of this mapping and clear out the entries of the old one- Ahora, ¿por qué querrías hacer algo así? Le recomiendo que revise su diseño y averigüe cómo llegó a este punto para empezar. No puedo pensar en ninguna razón en absoluto, de tener que replicar un mapeo y luego borrar el anterior.

Respuestas (1)

No es fácil evocar un ejemplo trivial. Según los comentarios, creo que las principales preocupaciones son aproximadamente:

  1. Limpie la información caducada de manera responsable y capture el reembolso de gas ofrecido por liberar el almacenamiento innecesario.
  2. Evite la iteración. https://blog.b9lab.com/getting-loopy-with-solidity-1d51794622ad
  3. Acceda a la información en O(1), por usuario, por época, globalmente por época o globalmente en general.

Es posible que tenga más caminos a considerar. Debe anticipar todas las consultas que el contrato debe realizar contra sus propios datos y encontrar una forma de acceder a la respuesta con una complejidad O (1).

Este enfoque es para darle algunas ideas:

  1. Necesitará una organización de datos que se adapte fácilmente al uso de conjuntos de ID que se refieran a mappingsde structs. Por lo general, funciona mejor tener un mapeo global de todo lo que existe, con listas de punteros que satisfagan cada caso que el contrato necesita para su propia lógica de búsqueda . Por ejemplo, una lista global de todos los ID de transacción que existen y listas similares para cada usuario, en cada época. Puede utilizar address(this)como "Usuario" para representar el conjunto global. Escribirá claves en varios lugares, pero los registros solo una vez.
  2. Querrá agregar otra dimensión a algunas partes de la estructura para agrupar lógicamente las cosas en épocas (por ejemplo, un año). Lo que debería borrarse lógicamente va a la capa Epoch y se inicializa a cero, naturalmente. Lo que no debe reiniciarse cada año va en la capa de arriba para que parezca llevar adelante. Para ser claros, es imperativo que los datos permanezcan en reposo porque la reorganización a gran escala no es factible ni deseable.
  3. Si ha terminado con los datos, debe ser un buen ciudadano y hacer la recolección de basura. Esto también lo ayudará a economizar el costo de la gasolina por usar el sistema, porque cada vez que un usuario elimina un valor distinto de cero, obtiene un reembolso de la gasolina. Tienes que hacer eso sin iteración. Una solución a ese problema es utilizar la estructura que existe para inspeccionar la Época caducada. Si aún existen datos, elimine un registro. Puede hacerlo cada vez que un usuario suministre gas para cualquier propósito agregando un modificador a las funciones de cambio de estado. Esto no debería objetar porque los usuarios obtienen un reembolso de gasolina si hay algo que eliminar.

Puede hacer la vida mucho más fácil con estas bibliotecas de conjuntos que se ocupan de las operaciones CRUD. https://github.com/rob-Hitchens/SetTypes . En caso de que el método utilizado no esté claro, consulte esta serie: https://medium.com/robhitchens/solidity-crud-epilogue-e563e794fde

Puede hacer todo tipo de manejo de datos como bibliotecas: listas vinculadas, listas ordenadas, etc. Para algo tan complejo como suenan sus proyectos, recomiendo separar esas preocupaciones de la aplicación de línea de negocio para reducir la repetición y mejorar la legibilidad y probablemente fiabilidad.

El método de recolección de basura sugerido se basa en la noción de amortización del trabajo que se explica aquí: https://medium.com/@weka/dividend-bearing-tokens-on-ethereum-42d01c710657 . La Época actual y el correcto funcionamiento del contrato es la principal preocupación, por supuesto. Debería ser irrelevante si se limpian los datos obsoletos, pero es una buena política hacerlo, y puede hacer que los usuarios sean recompensados ​​por eliminarlos hasta que se complete el proceso de limpieza.

Apenas:

modifier garbageCollection {
  if(previousEpochStillExists) {
    // get to work removing one txn/record and all references to it
  }
  if(nothingLeft)
    // remove the epoch itsef
  }
  _;
}

Como idea de último momento, piense detenidamente en minimizar el almacenamiento de estado. Puede inmortalizar entradas con registros de eventos (mucho más barato). Una buena heurística es limitar el almacenamiento de estado a los valores que el contrato necesita para su propia lógica interna. Nunca para mantenimiento de registros memre porque un cliente podría estar interesado. Hay formas más eficientes de hacerlo.

Espero eso ayude.