3

Hello it's been written many times, but as I observe none of the answers might be the one. I'm interested in the following thing. If in a smart contract I made the event , where will it be stored? in blockchain , if yes, where exactly?if not there, maybe in nodes? I'm building a serious app and i have to store many things there. so i have finally 2 questions.

1) where are logs stored exactly? 2)if in block header, can you describe how i can trust those logs getting in web3.js front-end?

2 Answers 2

4

You can trust the logs because they are stored in their respective block. If a log gets changed it would change the blockhash, receiptRoot, etc , same as with transactions. The merkle tree would not be the same anymore.

So if I execute a function at a certain block heights the receipt and thus the log will also be part of the block at that height as well as my transaction.

To answer where they are stored exactrly. In the transactions receipt trie if I recall correctly.

picture credit from https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369

Block header

2
  • Thanks for the nice explanation. All clear. I'd ask you one more question if you don't mind answering here. Ok? Thing is What needs to be done is about big data. as you know, smart contracts are not for big data, because of gas price raising. So One solution might be to store data in IPFS or logs. As one knows, It's much better to store it in logs, because it's still in blockchain and that's what people like. Question is : when I store lots of data there, one of the fields must be hidden and only i could get that. no one should go to blockchain and see the logs. What do you think?
    – Chemistry
    Aug 22, 2018 at 16:23
  • You can encrypt the field with your public key so that only your private key can decrypt it. The issue is if the encryption algo ever collides and thus guessing private keys becomes possible, because we can't delete data from a system like ethereum/ipfs/swarm. I would advise using Swarm (bzz in ethereum) over IPFS as it's more entangled with the ecosystem. But that's just personal preference after using both. Aug 22, 2018 at 17:04
3

When a contract emits an event, it is stored in the StateDB struct :

// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
type StateDB struct {
    db   Database
    trie Trie

    // This map holds 'live' objects, which will get modified while processing a state transition.
    stateObjects      map[common.Address]*stateObject
    stateObjectsDirty map[common.Address]struct{}

    // DB error.
    // State objects are used by the consensus core and VM which are
    // unable to deal with database-level errors. Any error that occurs
    // during a database read is memoized here and will eventually be returned
    // by StateDB.Commit.
    dbErr error

    // The refund counter, also used by state transitioning.
    refund uint64

    thash, bhash common.Hash
    txIndex      int
    logs         map[common.Hash][]*types.Log
    logSize      uint

    preimages map[common.Hash][]byte

    // Journal of state modifications. This is the backbone of
    // Snapshot and RevertToSnapshot.
    journal        *journal
    validRevisions []revision
    nextRevisionId int

    lock sync.Mutex
}

This is the member that holds the data in the above struct:

    logs         map[common.Hash][]*types.Log

During the block processing each transaction returns a receipt (with Event logs) and they are stored in an array (from core/state_processor.go):

// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
    statedb.Prepare(tx.Hash(), block.Hash(), i)
    receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
    if err != nil {
        return nil, nil, 0, err
    }
    receipts = append(receipts, receipt)
    allLogs = append(allLogs, receipt.Logs...)
}

Once the receipts are collected, the consensus algorithm calculates hash on all the recepits, and includes the resulting hash into the block's header. After that the block's hash is generated and distributed to other nodes.

The process for creating a new block is here (core/types/block.go)

func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
    b := &Block{header: CopyHeader(header), td: new(big.Int)}

    // TODO: panic if len(txs) != len(receipts)
    if len(txs) == 0 {
        b.header.TxHash = EmptyRootHash
    } else {
        b.header.TxHash = DeriveSha(Transactions(txs))
        b.transactions = make(Transactions, len(txs))
        copy(b.transactions, txs)
    }

    if len(receipts) == 0 {
        b.header.ReceiptHash = EmptyRootHash
    } else {
        b.header.ReceiptHash = DeriveSha(Receipts(receipts))
        b.header.Bloom = CreateBloom(receipts)
    }

    if len(uncles) == 0 {
        b.header.UncleHash = EmptyUncleHash
    } else {
        b.header.UncleHash = CalcUncleHash(uncles)
        b.uncles = make([]*Header, len(uncles))
        for i := range uncles {
            b.uncles[i] = CopyHeader(uncles[i])
        }
    }

    return b
}

This is the line that calculates the hash on the receipts:

b.header.ReceiptHash = DeriveSha(Receipts(receipts))

Events are stored as a part of the Receipt struct:

// Receipt represents the results of a transaction.
type Receipt struct {
    // Consensus fields
    PostState         []byte `json:"root"`
    Status            uint64 `json:"status"`
    CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
    Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
    Logs              []*Log `json:"logs"              gencodec:"required"`

    // Implementation fields (don't reorder!)
    TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
    ContractAddress common.Address `json:"contractAddress"`
    GasUsed         uint64         `json:"gasUsed" gencodec:"required"`
}

this is the array containing the events:

    Logs              []*Log `json:"logs"              gencodec:"required"`

And the envent is defined as:

// Log represents a contract log event. These events are generated by the LOG opcode and
// stored/indexed by the node.
type Log struct {
    // Consensus fields:
    // address of the contract that generated the event
    Address common.Address `json:"address" gencodec:"required"`
    // list of topics provided by the contract.
    Topics []common.Hash `json:"topics" gencodec:"required"`
    // supplied by the contract, usually ABI-encoded
    Data []byte `json:"data" gencodec:"required"`

    // Derived fields. These fields are filled in by the node
    // but not secured by consensus.
    // block in which the transaction was included
    BlockNumber uint64 `json:"blockNumber"`
    // hash of the transaction
    TxHash common.Hash `json:"transactionHash" gencodec:"required"`
    // index of the transaction in the block
    TxIndex uint `json:"transactionIndex" gencodec:"required"`
    // hash of the block in which the transaction was included
    BlockHash common.Hash `json:"blockHash"`
    // index of the log in the receipt
    Index uint `json:"logIndex" gencodec:"required"`

    // The Removed field is true if this log was reverted due to a chain reorganisation.
    // You must pay attention to this field if you receive logs through a filter query.
    Removed bool `json:"removed"`
}

All this data is tored in --datadir``/geth/chaindata directory

You can trust the event logs because the hash of all receipts is included into the Block's hash, so the data is cryptographically "sealed".

In case you want to validate the Receipts you get them with this function:

receipts:=core.GetBlockReceipts(ethereum.ChainDb(), block.Hash(), block.NumberU64())`

And recalculate the hash again. It must match the ReceiptsHash of the block header.

I hope this it is now clear about how events work internally.

2
  • Thanks for the nice explanation. All clear. I'd ask you one more question if you don't mind answering here. Ok? Thing is What needs to be done is about big data. as you know, smart contracts are not for big data, because of gas price raising. So One solution might be to store data in IPFS or logs. As one knows, It's much better to store it in logs, because it's still in blockchain and that's what people like. Question is : when I store lots of data there, one of the fields must be hidden and only i could get that. no one should go to blockchain and see the logs. What do you think?
    – Chemistry
    Aug 22, 2018 at 16:33
  • The concept of the blockchain technology is to have the data being public and accessible by anyone. So, the only way to store private data on the blockchain is encrypting it, However, encryption algorithms are constantly being broken by more advanced hardware and bugs, so there is a risk your data may become public in the future. You can store your private data on your own datacenter and publish the hash of this data on the blockhcain, this is one way to prove you didn't alter it, while storing privately.
    – Nulik
    Aug 22, 2018 at 16:47

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.