¿Cómo implementas PubSub sin quedarte sin gasolina (vector de ataque)?

Un patrón PubSub simple involucra una variedad de suscriptores. Cualquiera puede suscribirse. Una función de publicación hará algo por cada suscriptor. ¿Puedes ver el vector de ataque...?

contract PubSub
{
    address[] subscribers;

    function PubSub() {
    }

    function Subscribe() {
        subscribers.push(msg.sender);
    }

    function Publish(uint value) {

        // if a malicious user spams Subscribe then
        // this loop will never complete, rendering
        // this function unusable
        for(uint i=0; i<subscribers.length; i++) {
            // do something with subscribers[i]...
        }
    }
}

Un atacante puede llamar Subscribedesde numerosas direcciones hasta que la subscribersmatriz sea tan grande que Publishse vuelva inutilizable, siempre con un error de falta de gas.

¿Cuál es una buena manera de evitar esto, mientras se conservan las capacidades de suscripción de publicación? Estoy pensando que algún tipo de cola de prioridad basada en tarifas podría ser una solución, pero quiero verificar aquí primero para ver si alguien más ha encontrado este problema o tiene una buena solución en mente. ¡Gracias!

Respuestas (2)

Soy nuevo en la redacción de contratos así que, si digo algo estúpido, por favor, ignórenlo. Pero, según tengo entendido, si tiene un vector de ataque en el que las personas pueden extinguir sus recursos, entonces no está cobrando correctamente por sus recursos.

Cada vez que alguien se suscribe, aumenta el costo de todas las publicaciones posteriores, para siempre. ¿Derecha? Entonces, si no desea pasar ese costo al editor, el suscriptor debe pagarlo. Ahora bien, es obviamente imposible estimar el costo de la suscripción cuando podría haber llamadas subsiguientes potencialmente infinitas a publish. Bajo ese punto de vista, parece que limitar la cantidad de publicaciones que recibirá un suscriptor es la única respuesta lógica.

De esa manera, su función de suscripción recibiría un argumento adicional uint total_messages, para indicar cuántos mensajes futuros desea recibir el suscriptor. Cuando pasa eso, se da de baja automáticamente. Luego, solo encuentra el costo de suscripción como total_messages * message_cost, donde message_costse usa el gas para ejecutar una sola iteración del ciclo. Alternativamente, los suscriptores podrían tener un saldo del cual deduces una pequeña cantidad de Ether en cada publicación para cubrir los costos, cancelando la suscripción de un usuario cuando se queda sin Ether. O simplemente podría cobrarle al editor, pero eso hará que la publicación sea cada vez más costosa.

¿Tiene sentido?

Para evitar esto, simplemente divida su matriz en múltiples matrices cuando sea demasiado grande y publíquela en cada matriz en un bloque nuevo.

Esto resuelve el problema de alcanzar el límite de gasolina, pero no resuelve el problema de aumentar los costos. Para resolver ese problema, tienes algunas opciones. Lo más fácil es simplemente dividir los costos entre todos los usuarios del sistema: si un usuario no ha pagado sus cuotas, no se le publica.

¿Cómo se publica en un bloque nuevo? es decir, ¿cómo se divide el cálculo en múltiples transacciones/bloques?
Una forma es usar un servicio como el despertador Ethereum para automatizar esto. La otra forma es hacer que los usuarios de cada matriz sean responsables de activar el contrato en o después de su bloqueo. La publicación en cada matriz se puede llamar en cualquier momento, pero una vez que se llama a una matriz, las otras se bloquearán para ese bloque.
¿Cuál es la ventaja de dividir en varias matrices en lugar de permitir que la persona que llama a Publish() especifique los índices de los elementos que desea publicar?
@EdmundEdgar Realmente no obtendrías PubSub con eso (según entiendo tu propuesta), sería más QuerySub, en el que cada persona tenía que consultar todo el tiempo.