13

Let's say contract A has function F.

If someone uses a regular account to call A::F a transaction is laid down on the blockchain. That transaction is easily found on EtherScan for example.

If a contract account, say contract B, uses call to call the function A::F, this is called an "internal transaction," and this is not laid down as a transaction on the blockchain. So, for example, you would not see a transaction on contract A 'transactions' page on EtherScan or by interrogating the blockchain with something like web3.

Two questions:

1) Is what I said above correct?

2) Other than by clicking on EtherScan's 'Internal transactions' link, how can I possibly know about these 'internal' transactions?

In other words, what if I wanted to audit EtherScan by using the blockchain directly? Can I do that? How?

4 Answers 4

8

1) Is what I said above correct?

Yes.

2) Other than by clicking on EtherScan's 'Internal transactions' link, how can I possibly know about these 'internal' transactions?

This has come up a couple of times recently. Block explorers use their own instrumented versions of the EVM, so to do something similar you'd need to implement your own.

Edit:

As per this answer to one of the other threads, Parity v1.1 should include a feature to allow users to trace and inspect internal transactions.

3
  • At some point in the future, I envision a lot of auditors/accountants wanting to get at this type of information in a much easier way than 'instrumenting an EVM.' Are there any efforts under way to make this happen? Thanks so much for your response, Richard. I'm convinced you're correct, but I'll wait to mark this as answered for a couple of hours to see if anyone has anything more to say. Aug 25, 2016 at 15:28
  • I think you might have hit upon a business idea... Version 1.1 of Parity (aka Alacrity) was touted as including such a feature, though there's currently no documentation (see this bug). Aug 25, 2016 at 15:35
  • Or just what @BokkyPooBah said... :) Aug 25, 2016 at 15:51
7

1) Yes.

2) See following.

Here's a wallet executed internal transaction - 0xcad1183ecf7f278713858d92c06f00934f79a75eb5c852352a3ac35de07e151c.

I use the following to firstly extract the debugging information:

user@Kumquat:~$ geth --exec 'debug.traceTransaction("0xcad1183ecf7f278713858d92c06f00934f79a75eb5c852352a3ac35de07e151c")' attach >  0xcad1183ecf7f278713858d92c06f00934f79a75eb5c852352a3ac35de07e151c.txt

Looking in the output file, I search for all "CALL" instructions and these are:

1)

  }, {
      depth: 2,
      error: "",
      gas: 25007,
      gasCost: 109040,
      memory: ["0000000000000000000000000040a54aa449a9a8f4e8a0641a1caf76efcc0c8e", "0000000000000000000000000000000000000000000000000000000000000102", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000040a54aa449a9a8f4e8a0641a1caf76efcc0c8e", "0000000000000000000000000000000000000000000000004563918244f40000", "000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", "0000000000000000000000000000000000000000000000000000000000000080", "0000000000000000000000000000000000000000000000000000000000000000"],
      op: "CALL",
      pc: 2299,
      stack: ["00000000000000000000000000000000000000000000000000000000b61d27f6", "00000000000000000000000000000000000000000000000000000000000003c6", "000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", "0000000000000000000000000000000000000000000000004563918244f40000", "0000000000000000000000000000000000000000000000000000000000000084", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", "0000000000000000000000000000000000000000000000004563918244f40000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000004563918244f40000", "000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", "00000000000000000000000000000000000000000000000000000000000186a0"],
      storage: {
        0000000000000000000000000000000000000000000000000000000000000106: "0000000000000000000000000000000000000000000000004563918244f40000",
        0000000000000000000000000000000000000000000000000000000000000107: "000000000000000000000000000000000000000000000000000000000000422c"
      }
  }, {

2)

  }, {
      depth: 3,
      error: "",
      gas: 25007,
      gasCost: 76291,
      memory: ["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060"],
      op: "CALL",
      pc: 2761,
      stack: ["0000000000000000000000000000000000000000000000000000000000000966", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000980", "000000000000000000000000ff8fb62c8a7d3bb7b825c6b859020d4eac9ea998", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000003782dace9d900000", "000000000000000000000000807640a13483f8ac783c557fcdf27be11ea4ac7a", "0000000000000000000000000000000000000000000000000de0b6b3a7640000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000060", "0000000000000000000000000000000000000000000000000de0b6b3a7640000", "000000000000000000000000807640a13483f8ac783c557fcdf27be11ea4ac7a", "00000000000000000000000000000000000000000000000000000000000106b3"],
      storage: {}
  }, {

The following code from github.com/bokkypoobah/TheDAOData/getTheDAOCreatedTokenEventsWithNonZeroExtraBalance_v3#L65-L85 will detect Wallet contract execute(...) functions using the function signature 0xb61d27f6 which is on the stack of the 1) "CALL" trace above.

status.structLogs.forEach(function(e) {
  if (e.op == "CALL") {
    var stack = e.stack;
    extraBalanceAmount = web3.toBigNumber("0x" + stack[stack.length-3]);
    // baac5300 = createTokenProxy(address) - https://www.4byte.directory/signatures/?bytes4_signature=0xbaac5300
    if (stack[0].substring(56) == "baac5300") {
      tokenOwner = "0x" + stack[2].substring(24);
      createdBy = "Proxy";
    } else if (stack[0].substring(56) == "b61d27f6") {
      amount = web3.toBigNumber("0x" + stack[stack.length-3]);
    } else if (stack[0].substring(56) == "00000966") {
      if (("0x" + stack[3].substring(24)) != tokenOwner) {
        tokenOwner = "0x" + stack[3].substring(24);
        createdBy = "Wallet Contract";
      }
    }
  }
  if (e.error.length > 0) {
    error = e.error;
  }
});

And from https://github.com/ethereum/dapp-bin/wallet/wallet.sol#L344-L360 the execute(...) function has the following parameters:

function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {

which matches the function signature from 0xb61d27f6:

execute(address,uint256,bytes)

Note that a multisig wallet contract call is from a confirm(...) function. From github.com/ethereum/dapp-bin/wallet/wallet.sol#L362-L371:

// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
    if (m_txs[_h].to != 0) {
        m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
        MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
        delete m_txs[_h];
        return true;
    }
}

I saw this message two days ago New in Geth: Support for server-side transaction tracing with custom JS callbacks!, but have not had time to check out the new debug.traceTransaction(...) functionality:

[–]nickjohnsonEthereum - Nick Johnson[S] 22 points 2 days ago

tl;dr: You can now call debug.traceTransaction with a custom JavaScript function; that function gets executed for every step of the VM on the target transaction, letting you accumulate and transmit back only the details you care about from the traced transaction - for instance, you can return just the call graph, or just a list of successful value transfers.

1
  • 1
    Thanks @BokkyPooBah. You're first example isn't applicable because you already know the hash. In the example I'm talking about one wouldn't know that--it's what I'm looking for. That transaction is on contract B, which in my example may be totally unknown. I think your second example does work though, but then I would have to do that for all functions of contract A, which is okay, but I'm looking from a simple auditor/accountant perspective. I don't see auditors/accountants spending a whole lot of time parsing the trace of a stack. I'll look into tracing. Parity also has tracing now. Aug 25, 2016 at 16:26
1

Answering this specific question:

2) Other than by clicking on EtherScan's 'Internal transactions' link, how can I possibly know about these 'internal' transactions?

There's an alternate solution to a debugger or mining software features: Use Events. They are very cheap on gas, and can provide you with a pretty good trace of what happened. For example you can log all the inputs to a Solidity function and log any key branches or outputs of the Solidity function.

I pretty much never use a debugger and always use extensive Events.

This won't help you if you are calling someone else's code that doesn't have events. I'm still shocked how much code out there doesn't use events...

Documentation:

http://solidity.readthedocs.io/en/develop/contracts.html#events

2
  • Thanks. I get this totally, but if I'm "auditing" an already existing contract for example, I can't change what events are presented, so if the contract's author has not caused the contract to generate the events needed to accurately interpret what's going on, I'm out of luck. Oct 28, 2016 at 10:48
  • Yeah, you are in the unfortunate position of using other's code that has no event logging. I'm not sure I'd want to call into someone else's code that didn't have (a) proper testing and (b) proper event logging. Generally the two go together if you are doing it right. I'd be scared of buggy code that could come back and bite me.
    – Paul S
    Oct 30, 2016 at 23:08
-1

To answer your second question, you really have two main options for track internal transactions: (a) running a Parity node (can be expensive and take up to weeks to sync) or (b) using address activity tracking (such as internal transactions) from Alchemy Notify.

I'd highly recommend you check out Alchemy Notify--> it's free and will have you up and running in <5 minutes. Here's a link to their docs: https://docs.alchemyapi.io/alchemy/guides/using-webhooks#address-activity

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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