Wie implementiert man PubSub, ohne dass ihm das Benzin ausgeht (Angriffsvektor)?

Ein einfaches PubSub-Muster umfasst ein Array von Abonnenten. Abonnieren kann jeder. Eine Veröffentlichungsfunktion wird etwas für jeden Abonnenten tun. Können Sie den Angriffsvektor erkennen...?

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]...
        }
    }
}

Ein Angreifer kann Subscribevon zahlreichen Adressen aus anrufen, bis das subscribersArray so groß ist, dass Publishes unbrauchbar wird, wobei er immer einen Gasmangel-Fehler erhält.

Was ist ein guter Weg, um dies zu umgehen und gleichzeitig die Abonnementfunktionen für die Veröffentlichung beizubehalten? Ich denke, dass eine Art kostenpflichtige Prioritätswarteschlange eine Lösung sein könnte, aber ich möchte zuerst hier nachsehen, ob jemand anderes auf dieses Problem gestoßen ist oder eine gute Lösung im Sinn hat. Vielen Dank!

Antworten (2)

Ich bin neu im Schreiben von Verträgen, also wenn ich etwas Dummes sage, ignorieren Sie es bitte. Aber so wie ich es verstehe, wenn Sie einen Angriffsvektor haben, bei dem Menschen Ihre Ressourcen löschen können, dann berechnen Sie Ihre Ressourcen nicht korrekt.

Jedes Mal, wenn jemand ein Abonnement abschließt, erhöht das die Kosten aller nachfolgenden Veröffentlichungen für immer. Recht? Wenn Sie diese Kosten also nicht an den Herausgeber weitergeben möchten, muss der Abonnent sie bezahlen. Nun ist es offensichtlich unmöglich, die Kosten für ein Abonnement abzuschätzen, wenn es möglicherweise unendlich viele nachfolgende Aufrufe von geben könnte publish. Aus dieser Sicht scheint es, dass die Begrenzung der Anzahl der Veröffentlichungen, die ein Abonnent erhält, die einzig logische Antwort ist.

Auf diese Weise würde Ihre Abonnementfunktion ein zusätzliches Argument erhalten, uint total_messages, um anzugeben, wie viele zukünftige Nachrichten der Abonnent erhalten möchte. Wenn es das passiert, meldet er sich automatisch ab. Dann finden Sie nur die Abonnementkosten als total_messages * message_cost, wobei message_costdas Gas verwendet wird, um eine einzelne Iteration der Schleife auszuführen. Alternativ könnten Abonnenten ein Guthaben haben, von dem Sie bei jeder Veröffentlichung einen kleinen Betrag an Ether abziehen, um die Kosten zu decken und einen Benutzer abzumelden, wenn ihm Ether ausgeht. Oder Sie könnten stattdessen einfach den Verlag belasten - aber das wird das Veröffentlichen immer teurer werden.

Ist das sinnvoll?

Um dies zu vermeiden, teilen Sie Ihr Array einfach in mehrere Arrays auf, wenn es zu groß wird, und veröffentlichen Sie es in jedem Array in einem neuen Block.

Dies löst das Problem des Erreichens der Gasgrenze, aber es löst nicht das Problem der Eskalation der Kosten. Um dieses Problem zu lösen, haben Sie einige Möglichkeiten. Am einfachsten ist es, die Kosten einfach auf alle Benutzer des Systems aufzuteilen - wenn ein Benutzer seine Gebühren nicht bezahlt hat, wird er nicht veröffentlicht.

Wie veröffentlichen Sie auf einem neuen Block? dh wie teilen Sie die Berechnung auf mehrere Transaktionen/Blöcke auf?
Eine Möglichkeit besteht darin, einen Dienst wie den Ethereum-Wecker zu verwenden, um dies zu automatisieren. Die andere Möglichkeit besteht darin, die Benutzer in jedem Array für die Aktivierung des Vertrags bei oder nach ihrer Sperrung verantwortlich zu machen. Die Veröffentlichung in jedem Array könnte jederzeit aufgerufen werden, aber sobald ein Array aufgerufen wird, würden die anderen für dieses blockiert gesperrt.
Was ist der Vorteil der Aufteilung in mehrere Arrays gegenüber der Person, die Publish() aufruft, die Indizes der Elemente angeben zu lassen, die veröffentlicht werden sollen?
@EdmundEdgar Damit würdest du PubSub nicht wirklich bekommen (so wie ich deinen Vorschlag verstehe), es wäre eher QuerySub, bei dem jede Person jedes Mal abfragen müsste.