Skip to content

Commit

Permalink
event visibility config part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
tudor-malene committed Sep 16, 2024
1 parent ca6843e commit b5fb988
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 81 deletions.
55 changes: 43 additions & 12 deletions go/enclave/storage/enclavedb/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (

func WriteEventType(ctx context.Context, dbTX *sql.Tx, et *EventType) (uint64, error) {
res, err := dbTX.ExecContext(ctx, "insert into event_type (contract, event_sig, auto_visibility, public, topic1_can_view, topic2_can_view, topic3_can_view, sender_can_view) values (?, ?, ?, ?, ?, ?, ?, ?)",
et.ContractId, et.EventSignature.Bytes(), et.AutoVisibility, et.Public, et.Topic1CanView, et.Topic2CanView, et.Topic3CanView, et.SenderCanView)
et.Contract.Id, et.EventSignature.Bytes(), et.AutoVisibility, et.Public, et.Topic1CanView, et.Topic2CanView, et.Topic3CanView, et.SenderCanView)
if err != nil {
return 0, err
}
Expand All @@ -47,10 +47,12 @@ func WriteEventType(ctx context.Context, dbTX *sql.Tx, et *EventType) (uint64, e
return uint64(id), nil
}

func ReadEventType(ctx context.Context, dbTX *sql.Tx, contractId uint64, eventSignature gethcommon.Hash) (*EventType, error) {
var et EventType
err := dbTX.QueryRowContext(ctx, "select id, contract, event_sig, auto_visibility, public, topic1_can_view, topic2_can_view, topic3_can_view, sender_can_view from event_type where contract=? and event_sig=?",
contractId, eventSignature.Bytes()).Scan(&et.Id, &et.ContractId, &et.EventSignature, &et.AutoVisibility, &et.Public, &et.Topic1CanView, &et.Topic2CanView, &et.Topic3CanView, &et.SenderCanView)
func ReadEventType(ctx context.Context, dbTX *sql.Tx, contract *Contract, eventSignature gethcommon.Hash) (*EventType, error) {
var et EventType = EventType{Contract: contract}
err := dbTX.QueryRowContext(ctx,
"select id, event_sig, auto_visibility, public, topic1_can_view, topic2_can_view, topic3_can_view, sender_can_view from event_type where contract=? and event_sig=?",
contract.Id, eventSignature.Bytes(),
).Scan(&et.Id, &et.EventSignature, &et.AutoVisibility, &et.Public, &et.Topic1CanView, &et.Topic2CanView, &et.Topic3CanView, &et.SenderCanView)
if errors.Is(err, sql.ErrNoRows) {
// make sure the error is converted to obscuro-wide not found error
return nil, errutil.ErrNotFound
Expand Down Expand Up @@ -229,13 +231,11 @@ func loadLogs(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Add
query := "select et.event_sig, t1.topic, t2.topic, t3.topic, datablob, b.hash, b.height, tx.hash, tx.idx, log_idx, c.address" + " " + baseEventsJoin
var queryParams []any

// Add relevancy rules
// An event is considered relevant to all account owners whose addresses are used as topics in the event.
// In case there are no account addresses in an event's topics, then the event is considered relevant to everyone (known as a "lifecycle event").
query += " AND (et.public=true OR eoa1.address=? OR eoa2.address=? OR eoa3.address=?) "
queryParams = append(queryParams, requestingAccount.Bytes())
queryParams = append(queryParams, requestingAccount.Bytes())
queryParams = append(queryParams, requestingAccount.Bytes())
// Add visibility rules
visibQuery, visibParams := visibilityQuery(requestingAccount)

query += visibQuery
queryParams = append(queryParams, visibParams...)

query += whereCondition
queryParams = append(queryParams, whereParams...)
Expand Down Expand Up @@ -272,6 +272,37 @@ func loadLogs(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Add
return result, nil
}

func visibilityQuery(requestingAccount *gethcommon.Address) (string, []any) {
acc := requestingAccount.Bytes()

visibQuery := "AND ("
visibParams := make([]any, 0)

visibQuery += " et.public=true "

// For auto configs, an event is considered relevant to all account owners whose addresses are used as topics in the event.
visibQuery += " OR (et.auto_visibility=true AND (eoa1.address=? OR eoa2.address=? OR eoa3.address=?))"
visibParams = append(visibParams, acc)
visibParams = append(visibParams, acc)
visibParams = append(visibParams, acc)

// Configured events
visibQuery += " OR (" +
"et.auto_visibility=false AND et.public=false AND " +
" (" +
" (et.topic1_can_view AND eoa1.address=?) " +
" OR (et.topic2_can_view AND eoa2.address=?) " +
" OR (et.topic3_can_view AND eoa3.address=?)" +
" )" +
")"
visibParams = append(visibParams, acc)
visibParams = append(visibParams, acc)
visibParams = append(visibParams, acc)

visibQuery += ") "
return visibQuery, visibParams
}

func WriteEoa(ctx context.Context, dbTX *sql.Tx, sender gethcommon.Address) (uint64, error) {
insert := "insert into externally_owned_account (address) values (?)"
res, err := dbTX.ExecContext(ctx, insert, sender.Bytes())
Expand Down
28 changes: 27 additions & 1 deletion go/enclave/storage/enclavedb/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package enclavedb
import (
"context"
"database/sql"
"fmt"

gethcommon "github.com/ethereum/go-ethereum/common"

Expand All @@ -27,13 +28,38 @@ type Contract struct {
Transparent *bool
}

func (contract Contract) IsTransparent() bool {
return contract.Transparent != nil && *contract.Transparent
}

// EventType - maps to the “event_type“ table
type EventType struct {
Id uint64
ContractId uint64
Contract *Contract
EventSignature gethcommon.Hash
AutoVisibility bool
Public bool
Topic1CanView, Topic2CanView, Topic3CanView *bool
SenderCanView *bool
}

func (et EventType) Auto() bool {
return et.Contract.AutoVisibility && et.AutoVisibility
}

func (et EventType) IsPublic() bool {
return (et.Contract.Transparent != nil && *et.Contract.Transparent) || et.Public
}

func (et EventType) IsTopicRelevant(topicNo int) bool {
switch topicNo {
case 1:
return et.Topic1CanView != nil && *et.Topic1CanView
case 2:
return et.Topic2CanView != nil && *et.Topic2CanView
case 3:
return et.Topic3CanView != nil && *et.Topic3CanView
}
// this should not happen under any circumstance
panic(fmt.Sprintf("unknown topic no: %d", topicNo))
}
157 changes: 89 additions & 68 deletions go/enclave/storage/events_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,20 @@ func (es *eventsStorage) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql

// store the contracts created by this tx
for createdContract, cfg := range txExecResult.CreatedContracts {
ctrId, err := es.storeNewContract(ctx, dbTX, createdContract, senderId, cfg)
_, err := es.storeNewContract(ctx, dbTX, createdContract, senderId, cfg)
if err != nil {
return err
}

c, err := es.readContract(ctx, dbTX, createdContract)
if err != nil {
return err
}

// create the event types for the events that were configured
for eventSig, eventCfg := range cfg.EventConfigs {
_, err = enclavedb.WriteEventType(ctx, dbTX, &enclavedb.EventType{
ContractId: *ctrId,
Contract: c,
EventSignature: eventSig,
AutoVisibility: eventCfg.AutoConfig,
Public: eventCfg.Public,
Expand Down Expand Up @@ -110,7 +115,7 @@ func (es *eventsStorage) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTx
eventType, err := es.readEventType(ctx, dbTX, l.Address, eventSig)
if errors.Is(err, errutil.ErrNotFound) {
// this is the first type an event of this type is emitted, so we must store it
eventType, err = es.storeEventType(ctx, dbTX, contract, l)
eventType, err = es.storeAutoConfigEventType(ctx, dbTX, contract, l)
if err != nil {
return fmt.Errorf("could not write event type. cause %w", err)
}
Expand All @@ -119,7 +124,7 @@ func (es *eventsStorage) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTx
return fmt.Errorf("could not read event type. Cause: %w", err)
}

topicIds, err := es.storeTopics(ctx, dbTX, l)
topicIds, err := es.storeTopics(ctx, dbTX, eventType, l)
if err != nil {
return fmt.Errorf("could not store topics. cause: %w", err)
}
Expand All @@ -138,27 +143,18 @@ func (es *eventsStorage) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTx
}

// handles the visibility config detection
func (es *eventsStorage) storeEventType(ctx context.Context, dbTX *sql.Tx, contract *enclavedb.Contract, l *types.Log) (*enclavedb.EventType, error) {
eventType := enclavedb.EventType{ContractId: contract.Id, EventSignature: l.Topics[0], AutoVisibility: contract.AutoVisibility}
func (es *eventsStorage) storeAutoConfigEventType(ctx context.Context, dbTX *sql.Tx, contract *enclavedb.Contract, l *types.Log) (*enclavedb.EventType, error) {
eventType := enclavedb.EventType{
Contract: contract,
EventSignature: l.Topics[0],
Public: contract.IsTransparent(),
}

// when the contract is transparent, all events are public
switch {
case contract.Transparent != nil && *contract.Transparent:
eventType.Public = true
case contract.AutoVisibility:
// autodetect based on the topics
isPublic, t1, t2, t3, err := es.autodetectVisibility(ctx, dbTX, l)
if err != nil {
return nil, fmt.Errorf("could not auto detect visibility for event type. cause: %w", err)
}
eventType.Public = *isPublic
eventType.Topic1CanView = t1
eventType.Topic2CanView = t2
eventType.Topic3CanView = t3
default:
// todo
return nil, fmt.Errorf("not implemented")
// event types that are not public - will have the default rules
if !eventType.Public {
eventType.AutoVisibility = true
}

id, err := enclavedb.WriteEventType(ctx, dbTX, &eventType)
if err != nil {
return nil, fmt.Errorf("could not write event type. cause: %w", err)
Expand All @@ -167,75 +163,108 @@ func (es *eventsStorage) storeEventType(ctx context.Context, dbTX *sql.Tx, contr
return &eventType, nil
}

func (es *eventsStorage) autodetectVisibility(ctx context.Context, dbTX *sql.Tx, l *types.Log) (*bool, *bool, *bool, *bool, error) {
isPublic := true
topicsCanView := make([]bool, 3)
func (es *eventsStorage) storeTopics(ctx context.Context, dbTX *sql.Tx, eventType *enclavedb.EventType, l *types.Log) ([]*uint64, error) {
topicIds := make([]*uint64, 3)
// iterate the topics containing user values
// reuse them if already inserted
// if not, discover if there is a relevant externally owned address
for i := 1; i < len(l.Topics); i++ {
topic := l.Topics[i]
// first check if there is an entry already for this topic
_, relAddressId, err := es.findEventTopic(ctx, dbTX, topic.Bytes())
eventTopicId, _, err := es.findEventTopic(ctx, dbTX, topic.Bytes())
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return nil, nil, nil, nil, fmt.Errorf("could not read the event topic. Cause: %w", err)
return nil, fmt.Errorf("could not read the event topic. Cause: %w", err)
}
if errors.Is(err, errutil.ErrNotFound) {
// check whether the topic is an EOA
relAddressId, err = es.findRelevantAddress(ctx, dbTX, topic)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return nil, nil, nil, nil, fmt.Errorf("could not read relevant address. Cause %w", err)
// if no entry was found
eventTopicId, err = es.storeEventTopic(ctx, dbTX, eventType, i, topic)
if err != nil {
return nil, fmt.Errorf("could not read the event topic. Cause: %w", err)
}
}
if relAddressId != nil {
isPublic = false
topicsCanView[i-1] = true
topicIds[i-1] = &eventTopicId
}
return topicIds, nil
}

// this function contains visibility logic
func (es *eventsStorage) storeEventTopic(ctx context.Context, dbTX *sql.Tx, eventType *enclavedb.EventType, i int, topic gethcommon.Hash) (uint64, error) {
relevantAddress, err := es.visibililty(ctx, dbTX, eventType, i, topic)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return 0, fmt.Errorf("could not determine visibility rules. cause: %w", err)
}

var relAddressId *uint64
if relevantAddress != nil {
var err error
relAddressId, err = es.readEOA(ctx, dbTX, *relevantAddress)
if err != nil {
return 0, err
}
}
return &isPublic, &topicsCanView[0], &topicsCanView[1], &topicsCanView[2], nil
eventTopicId, err := enclavedb.WriteEventTopic(ctx, dbTX, &topic, relAddressId)
if err != nil {
return 0, fmt.Errorf("could not write event topic. Cause: %w", err)
}
return eventTopicId, nil
}

func (es *eventsStorage) storeTopics(ctx context.Context, dbTX *sql.Tx, l *types.Log) ([]*uint64, error) {
topicIds := make([]*uint64, 3)
// iterate the topics containing user values
// reuse them if already inserted
// if not, discover if there is a relevant externally owned address
for i := 1; i < len(l.Topics); i++ {
topic := l.Topics[i]
// first check if there is an entry already for this topic
eventTopicId, _, err := es.findEventTopic(ctx, dbTX, topic.Bytes())
func (es *eventsStorage) visibililty(ctx context.Context, dbTX *sql.Tx, eventType *enclavedb.EventType, i int, topic gethcommon.Hash) (*gethcommon.Address, error) {
var relevantAddress *gethcommon.Address
switch {
case eventType.AutoVisibility:
var err error
// if there is no configuration, we have to autodetect the address
relevantAddress, err = es.autoDetectRelevantAddress(ctx, dbTX, topic)
if err != nil {
return nil, err
}

// when autodetecting, we assume that any address that is not a contract is an EOA
_, err = es.readEOA(ctx, dbTX, *relevantAddress)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return nil, fmt.Errorf("could not read the event topic. Cause: %w", err)
return nil, err
}
if errors.Is(err, errutil.ErrNotFound) {
// check whether the topic is an EOA
relAddressId, err := es.findRelevantAddress(ctx, dbTX, topic)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return nil, fmt.Errorf("could not read relevant address. Cause %w", err)
}
eventTopicId, err = enclavedb.WriteEventTopic(ctx, dbTX, &topic, relAddressId)
_, err := enclavedb.WriteEoa(ctx, dbTX, *relevantAddress)
if err != nil {
return nil, fmt.Errorf("could not write event topic. Cause: %w", err)
return nil, err
}
}
topicIds[i-1] = &eventTopicId

case eventType.IsPublic():
// for public events, there is no relevant address
relevantAddress = nil

case eventType.IsTopicRelevant(i):
relevantAddress = common.ExtractPotentialAddress(topic)
if relevantAddress == nil {
return nil, fmt.Errorf("invalid configuration. expected address in topic %d : %s", i, topic.String())
}

default:
es.logger.Crit("impossible case. Should not get here")
}
return topicIds, nil

return relevantAddress, nil
}

// Of the log's topics, returns those that are (potentially) user addresses. A topic is considered a user address if:
// - It has at least 12 leading zero bytes (since addresses are 20 bytes long, while hashes are 32) and at most 22 leading zero bytes
// - It is not a smart contract address
func (es *eventsStorage) findRelevantAddress(ctx context.Context, dbTX *sql.Tx, topic gethcommon.Hash) (*uint64, error) {
func (es *eventsStorage) autoDetectRelevantAddress(ctx context.Context, dbTX *sql.Tx, topic gethcommon.Hash) (*gethcommon.Address, error) {
potentialAddr := common.ExtractPotentialAddress(topic)
if potentialAddr == nil {
return nil, errutil.ErrNotFound
}

// first check whether there is already an entry in the EOA table
eoaID, err := es.readEOA(ctx, dbTX, *potentialAddr)
_, err := es.readEOA(ctx, dbTX, *potentialAddr)
if err != nil && !errors.Is(err, errutil.ErrNotFound) {
return nil, err
}
if err == nil {
return eoaID, nil
return potentialAddr, nil
}

// if the address is a contract then it's clearly not an EOA
Expand All @@ -247,15 +276,7 @@ func (es *eventsStorage) findRelevantAddress(ctx context.Context, dbTX *sql.Tx,
return nil, errutil.ErrNotFound
}

// when we reach this point, the value looks like an address, but we haven't yet seen it
// for the first iteration, we'll just assume it's an EOA
// we can make this smarter by passing in more information about the event
id, err := enclavedb.WriteEoa(ctx, dbTX, *potentialAddr)
if err != nil {
return nil, err
}

return &id, nil
return potentialAddr, nil
}

func (es *eventsStorage) readEventType(ctx context.Context, dbTX *sql.Tx, contractAddress gethcommon.Address, eventSignature gethcommon.Hash) (*enclavedb.EventType, error) {
Expand All @@ -266,7 +287,7 @@ func (es *eventsStorage) readEventType(ctx context.Context, dbTX *sql.Tx, contra
if err != nil {
return nil, err
}
return enclavedb.ReadEventType(ctx, dbTX, contract.Id, eventSignature)
return enclavedb.ReadEventType(ctx, dbTX, contract, eventSignature)
})
}

Expand Down

0 comments on commit b5fb988

Please sign in to comment.