Returning a result from a non-constant function?

daboozdabooz Member Posts: 17
I have a contract with functions that modify contract state. Those functions are not supposed to be marked constant. When those functions are invoked, they generate a transaction to the blockchain. Is it possible for those functions to return a result, and if so, how do I get it?

thanks

Comments

  • sillytunasillytuna Member Posts: 38 ✭✭
    They can return results directly to another contract, but not to an off chain caller. You can use events or public variables instead.
  • daboozdabooz Member Posts: 17
    @sillytuna thanks. In order to return a result to a calling contract, does the calling contract function have to also be non-constant so that both function calls happen in the same block chain transaction?

    Public variables are not an option for my use case.

    Is there an example of how to use events? The only examples I see are tied to javascript and web apps, but that's not my use case.

    thanks
  • sillytunasillytuna Member Posts: 38 ✭✭
    I've not tried a constant function calling a non-constant function but I'd imagine it's going to behave as non-constant and should be labelled as such. Since functions can be called 'live' from contracts I don't know how ethereum behaves in that situation.

    Events are described in the tutorials and are for communicating from the contract externally by logging into the blockchain. They're not for contracts talking to other contracts. AFAIK a contract can see an event.

    What's the problem with public vars?
  • MetalMetal Member Posts: 17
    edited September 2015
    When a function is called as a constant call, it has access to the same contract state it would in a transaction, but state changes won't be remembered.

    So a function called as a constant that calls a non-constant function is running locally in the calling node, has access to the state both functions needs, but won't remember any changes made. (The constant modifier on solidity is purely advisory. You can force a call of either type on any exposed contract method.)

    In practice, when I want to see my transaction calls' return values, I end up calling them twice, once as a constant call, once as a transaction call (that order matters). It is possible that the return value from the constant call won't match what the return value of the transaction call would have been, particularly if you use block number values, and probably in other conditions as well, so it's not exactly the most reliable way to go about it.

    In the long run, I'm hoping the web3 layer will acquire the ability to get return values from transaction calls somehow.

    Events are really quite neat, and not as costly as you'd expect gas wise (about an order of magnitude less than storing a value, if I remember right.)
    To throw another work-around at you, if events aren't your cup of tea, you could stash your intended return data somewhere private, then have a constant call to access it with the appropriate checks in front of it.
    That won't make it any more secret to nodes, but contracts wouldn't be able to trivially grab it at least.



  • sillytunasillytuna Member Posts: 38 ✭✭
    I use a contract management system to ensure that contracts only talk to other contracts to which they've been allowed access. However, this does complicate deployment somewhat and it's more about protecting misuse than from read access to data.

    Interesting post Metal - I didn't realise you could do that.
  • daboozdabooz Member Posts: 17
    @Metal and @sillytuna, thanks for the replies.
    The constant vs. non-constant discussion was helpful and clarified a few things for me. For anyone used to writing transactional (i.e. 2PC ACID) applications, constant functions calling non-constant functions is a common and acceptable pattern as long as you realize that the constant function might have to do some compensation if you get errors after the non-constant function returns. But what I've learned is that this pattern is problematic with ethereum, and it's a pattern that should probably be avoided.

    WRT events, I finally figured it out and yes they are a nice tool to have. Where I was hung up was in the way that the doc talks about events, it discusses them in relation to logging. What I didn't realize is that the doc meant transactional logging (this is the first secret) not application or system logging. Events in ethereum are really just a view into the blockchain of the transactional happenings of a contract. Further, in the RPC API there is no concept of an event listener, which is what I was looking for. The second secret is to know that you have to create a filter in order to receive any events. Further, you never receive events, you have to poll for them. That's completely counter-intuitive for an eventing system, usually with eventing systems you create a listener and then filter the event stream to only what you want to see. By common definition, a filter eliminates unwanted things, it doesn't enable new things. Anyway, I discovered the secrets and was able to get it to work. Your posts gave me enough motivation to look at events a second time.
  • MetalMetal Member Posts: 17
    Constant functions calling transactional functions just won't work as you'd expect. Currently contracts can't initiate a brand new transaction. Those have to come from the Outside.

    Which is a shame because if that worked, there would be some interesting use cases where you could offload heavy computational loads into constant calls that cost no gas, then fire up transactions from there to do the state-keeping bits needed. On the blockchain, it would look like only the latter transaction happened, with all the right precomputed parameters fed as input.

    I'm guessing the problem to solve for that to happen is that the contract would effectively start a transaction on the caller's behalf, which could lead to unpleasant surprises. There'd probably be a need for a 3rd type of contract calling, "constant with option to transact", which would set ether and gas to use should the contract decide to start a transaction on the caller's behalf.

    regarding RPC filters, I agree the polling API feels a bit icky. Or maybe just out of fashion. Maybe it could evolve some websocket support, but it's not clear how badly that's needed. New blocks appear every ~12 seconds, as do receipts and the logs in them, so having web3.js poll every few second over a local link isn't the worst thing.

    It does however mean that playing Quake 3 on the blockchain will remain unbearably laggy for the time being.



  • daboozdabooz Member Posts: 17
    @Metal said
    "I'm guessing the problem to solve for that to happen is that the contract would effectively start a transaction on the caller's behalf, which could lead to unpleasant surprises."

    Right, or provide an API to enable the application to control the begin and end of transactions. Just about every programming model that can do transactions has been able to do this going back at least 30 years, so yes, it's disappointing that this aspect is missing.
  • fivedogitfivedogit Member Posts: 21
    sillytuna said:

    They can return results directly to another contract, but not to an off chain caller. You can use events or public variables instead.

    Are you sure this is true? I'm trying to test it right now. In Solidity,
    .call("contractFunction", someval) always returns boolean according to the compiler. And I assume that boolean is just "did the call succeed?" rather than any value you can actually return.

    See https://github.com/fivedogit/solidity-baby-steps/tree/master/contracts "Ping" and "Pong".
  • MetalMetal Member Posts: 17
    Don't use .call if you can avoid it.

    Here's ping and pong slightly rewritten to leverage builtin contracts and public values, and to call methods on other contracts directly.
    Note that although they call each other, they don't have to be compiled together as long as the "hasPongval" contract is included with each one (which defines the interface ping can call on pong, roughly.)
    import "mortal";
    
    contract hasPongval {
        int8 public pongval;
    }
    
    contract Ping is mortal, hasPongval {
        hasPongval pong;
        int8 public pongval = -1;
        
        function Ping(hasPongval addr) {
            pong = addr;
        }
        
        function getPongvalRemote() {
            pongval = pong.pongval();
        }
    }
    
    contract Pong is mortal, hasPongval {
        function setPongval(int8 value) {
            pongval = value;
        }
    }
    To test this with minimal fuss, I suggest throwing it into https://chriseth.github.io/browser-solidity/ , then create Pong, setPongval to something, then create Ping using the address of your Pong instance (has to be passed as "0x..." rather than the raw number returned), then call getPongvalRemote() on it, then pongVal() to see if it made it across.
  • fivedogitfivedogit Member Posts: 21
    edited September 2015
    Very helpful, but I'm still not sure about some things.
    import "mortal";
    
    contract hasPongval {
        int8 public pongval;
    }
    
    contract Ping is mortal, hasPongval {
        hasPongval pong;
        int8 public pongval=-1; // pongval def'd, but also in inherited 'hasPongval'?
        
        function Ping(hasPongval addr) {  
            pong = addr;
        }
        
        function getPongvalRemote() {
            pongval = pong.pongval();      // How does Pong have a pongval method()?
            
        }
    }
    
    // Looks like it only has setPongval(int8 v) and a public pongval value. No method.
    contract Pong is mortal, hasPongval {
        function setPongval(int8 value) {
            pongval = value;
        }
    }
    Update: I've edited my code on github to reflect your feedback almost exactly, But it still doesn't work. If I start Ping with -1 and Pong with -32, then I get:

    > ping.getPongval();
    -1
    > pong.getPongvalConstant();
    -32
    > ping.getPongvalRemote.sendTransaction({from:eth.coinbase,gas:1000000});
    "0x02af62f1b3e690e6c2f0c865383cbac960ce9bc6dd1fa8e3a761f1b3de2aa105"
    > ping.getPongval();
    0
    > pong.getPongvalConstant();
    -32
    Post edited by fivedogit on
  • MetalMetal Member Posts: 17
    Solidity generates constant getter methods for any public values.

    So uint32 public test; will generate a
    function test() constant returns (uint32) { return test; }

    While mapping (address=>uint32[]) public numbers; will generate a
    function numbers(address key, uint arrayIndex) constant returns (uint32) { return numbers[key][arrayIndex]; }

    Solidity will also collapse duplicate declarations in derived contracts, so while it may not be the best style to define pongval twice, it should produce exactly the same code as if it was defined once.

    I can't easily spot the root cause of what you're seeing. I did try my code in the solidity browser page and saw it working.
Sign In or Register to comment.