5

A simple PubSub pattern involves an array of subscribers. Anyone can subscribe. A publish function will do something for each subscriber. Can you spot the attack vector...?

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

An attacker can call Subscribe from numerous addresses until the subscribers array is so large that Publish will become unusable, always hitting an out-of-gas error.

What is a good way around this, while retaining publish subscription capabilities? I am thinking that some kind of fee-based priority queue might be a solution, but I want to check here first to see if anyone else has encountered this problem or has a good solution in mind. Thanks!

4

I'm new to writing contracts so, if I say anything stupid, please, disregard. But, as I understand it, if you got an attack vector where people are able to extinguish your resources, then you're not charging correctly for your resources.

Every time someone subscribes, that increases the cost of all subsequent publishings, forever. Right? So, if you don't want to pass that cost to the publisher, the subscriber needs to pay it. Now, it is obviously impossible to estimate a cost of subscribing when there could be potentially infinite subsequent calls to publish. Under that view, it seems that limiting the amount of the publishes a subscriber will receive is the only logical answer.

That way, your subscribe function would receive an additional argument, uint total_messages, to indicate how many future messages the subscriber wants to receive. When it passes that, he automatically unsubs. Then, you just find the subscribing cost as total_messages * message_cost, where message_cost is the gas used to run a single iteration of the loop. Alternatively, subbers could have a balance from which you deduct a little amount of Ether on each publish to cover the costs, unsubscribing an user when he runs out of Ether. Or you could just charge the publisher instead - but that will make publishing become more and more expensive.

Does that make sense?

3

To avoid this, simply split your array up into multiple arrays when it gets too large, and publish to each array on a new block.

This solves the problem of hitting the gas limit, but it doesn't solve the problem of escalating the costs. To solve that problem, you have a few options. The easiest is simply to split the costs between all users of the system - if a user hasn't paid their dues, they don't get published to.

  • How do you publish on a new block? i.e. how do you split computation across multiple transactions/blocks? – Raine Revere May 8 '16 at 19:00
  • One way is to use a service like Ethereum alarm clock to automate this. The other way is to have the users in each array be responsible for activating the contract on or after their block. The publishing to each array could be called at any time, but once one array is called, the others would be locked for that blocked. – Matt May 8 '16 at 19:08
  • What's the advantage of splitting into multiple arrays over letting the person calling Publish() specify the indexes of the items the want to publish? – Edmund Edgar May 8 '16 at 23:02
  • @EdmundEdgar You wouldn't really get PubSub with that (as I understand your proposal), it'd be more QuerySub, in which each person had to query every time. – Matt May 10 '16 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.