diff --git a/go/enclave/components/batch_registry.go b/go/enclave/components/batch_registry.go index 9fdbf06d69..1d8d01d90b 100644 --- a/go/enclave/components/batch_registry.go +++ b/go/enclave/components/batch_registry.go @@ -92,8 +92,9 @@ func (br *batchRegistry) OnBatchExecuted(batchHeader *common.BatchHeader, receip defer br.callbackMutex.RUnlock() txs, err := br.storage.FetchBatchTransactionsBySeq(context.Background(), batchHeader.SequencerOrderNo.Uint64()) - if err != nil { - br.logger.Crit("cannot get transactions. ", log.ErrKey, err) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + // this function is called after a batch was successfully executed. This is a catastrophic failure + br.logger.Crit("should not happen. cannot get transactions. ", log.ErrKey, err) } br.headBatchSeq = batchHeader.SequencerOrderNo if br.batchesCallback != nil { diff --git a/go/enclave/storage/enclavedb/batch.go b/go/enclave/storage/enclavedb/batch.go index a2a91dfb83..444f6b05e6 100644 --- a/go/enclave/storage/enclavedb/batch.go +++ b/go/enclave/storage/enclavedb/batch.go @@ -19,7 +19,7 @@ import ( ) const ( - queryReceipts = "select exec_tx.receipt, tx.content, batch.hash, batch.height from exec_tx join tx on tx.id=exec_tx.tx join batch on batch.sequence=exec_tx.batch " + queryReceipts = "select receipt.content, tx.content, batch.hash, batch.height from receipt join tx on tx.id=receipt.tx join batch on batch.sequence=receipt.batch " ) func WriteBatchHeader(ctx context.Context, dbtx *sql.Tx, batch *core.Batch, convertedHash gethcommon.Hash, blockId int64, isCanonical bool) error { @@ -46,13 +46,25 @@ func WriteBatchHeader(ctx context.Context, dbtx *sql.Tx, batch *core.Batch, conv return err } +func UpdateCanonicalBatch(ctx context.Context, dbtx *sql.Tx, isCanonical bool, blocks []common.L1BlockHash) error { + args := make([]any, 0) + args = append(args, isCanonical) + for _, blockHash := range blocks { + args = append(args, blockHash.Bytes()) + } + + updateBatches := "update batch set is_canonical=? where " + repeat(" l1_proof_hash=? ", "OR", len(blocks)) + _, err := dbtx.ExecContext(ctx, updateBatches, args...) + return err +} + func ExistsBatchAtHeight(ctx context.Context, dbTx *sql.Tx, height *big.Int) (bool, error) { - var count int - err := dbTx.QueryRowContext(ctx, "select count(*) from batch where height=?", height.Uint64()).Scan(&count) + var exists bool + err := dbTx.QueryRowContext(ctx, "select exists(select 1 from batch where height=?)", height.Uint64()).Scan(&exists) if err != nil { return false, err } - return count > 0, nil + return exists, nil } // WriteTransactions - persists the batch and the transactions @@ -111,8 +123,8 @@ func MarkBatchExecuted(ctx context.Context, dbtx *sql.Tx, seqNo *big.Int) error return err } -func WriteExecutedTransaction(ctx context.Context, dbtx *sql.Tx, batchSeqNo uint64, txId *uint64, createdContract *uint64, receipt []byte) (uint64, error) { - insert := "insert into exec_tx (created_contract_address, receipt, tx, batch) values " + "(?,?,?,?)" +func WriteReceipt(ctx context.Context, dbtx *sql.Tx, batchSeqNo uint64, txId *uint64, createdContract *uint64, receipt []byte) (uint64, error) { + insert := "insert into receipt (created_contract_address, content, tx, batch) values " + "(?,?,?,?)" res, err := dbtx.ExecContext(ctx, insert, createdContract, receipt, txId, batchSeqNo) if err != nil { return 0, err @@ -289,8 +301,7 @@ func selectReceipts(ctx context.Context, db *sql.DB, config *params.ChainConfig, } func ReadReceipt(ctx context.Context, db *sql.DB, txHash common.L2TxHash, config *params.ChainConfig) (*types.Receipt, error) { - // todo - canonical? - row := db.QueryRowContext(ctx, queryReceipts+" where tx.hash=? ", txHash.Bytes()) + row := db.QueryRowContext(ctx, queryReceipts+" where batch.is_canonical=true AND tx.hash=? ", txHash.Bytes()) // receipt, tx, batch, height var receiptData []byte var txData []byte @@ -327,7 +338,7 @@ func ReadReceipt(ctx context.Context, db *sql.DB, txHash common.L2TxHash, config func ReadTransaction(ctx context.Context, db *sql.DB, txHash gethcommon.Hash) (*types.Transaction, common.L2BatchHash, uint64, uint64, error) { row := db.QueryRowContext(ctx, - "select tx.content, batch.hash, batch.height, tx.idx from exec_tx join tx on tx.id=exec_tx.tx join batch on batch.sequence=exec_tx.batch where batch.is_canonical=true and tx.hash=?", + "select tx.content, batch.hash, batch.height, tx.idx from receipt join tx on tx.id=receipt.tx join batch on batch.sequence=receipt.batch where batch.is_canonical=true and tx.hash=?", txHash.Bytes()) // tx, batch, height, idx @@ -421,7 +432,7 @@ func GetTransactionsPerAddress(ctx context.Context, db *sql.DB, config *params.C } func CountTransactionsPerAddress(ctx context.Context, db *sql.DB, address *gethcommon.Address) (uint64, error) { - row := db.QueryRowContext(ctx, "select count(1) from exec_tx join tx on tx.id=exec_tx.tx join batch on batch.sequence=exec_tx.batch "+" where tx.sender_address = ?", address.Bytes()) + row := db.QueryRowContext(ctx, "select count(1) from receipt join tx on tx.id=receipt.tx join batch on batch.sequence=receipt.batch "+" where tx.sender_address = ?", address.Bytes()) var count uint64 err := row.Scan(&count) diff --git a/go/enclave/storage/enclavedb/block.go b/go/enclave/storage/enclavedb/block.go index 41e22ca8df..2e9cd6e35e 100644 --- a/go/enclave/storage/enclavedb/block.go +++ b/go/enclave/storage/enclavedb/block.go @@ -10,8 +10,6 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/ten-protocol/go-ten/go/common" @@ -33,29 +31,16 @@ func WriteBlock(ctx context.Context, dbtx *sql.Tx, b *types.Header) error { return err } -func UpdateCanonicalValue(ctx context.Context, dbtx *sql.Tx, isCanonical bool, blocks []common.L1BlockHash, _ gethlog.Logger) error { - currentBlocks := repeat(" hash=? ", "OR", len(blocks)) - +func UpdateCanonicalBlock(ctx context.Context, dbtx *sql.Tx, isCanonical bool, blocks []common.L1BlockHash) error { args := make([]any, 0) args = append(args, isCanonical) for _, blockHash := range blocks { args = append(args, blockHash.Bytes()) } - updateBlocks := "update block set is_canonical=? where " + currentBlocks + updateBlocks := "update block set is_canonical=? where " + repeat(" hash=? ", "OR", len(blocks)) _, err := dbtx.ExecContext(ctx, updateBlocks, args...) - if err != nil { - return err - } - - updateBatches := "update batch set is_canonical=? where " + repeat(" l1_proof_hash=? ", "OR", len(blocks)) - // updateBatches := "update batch set is_canonical=? where l1_proof in (select id from block where " + currentBlocks + ")" - _, err = dbtx.ExecContext(ctx, updateBatches, args...) - if err != nil { - return err - } - - return nil + return err } func IsCanonicalBlock(ctx context.Context, dbtx *sql.Tx, hash *gethcommon.Hash) (bool, error) { diff --git a/go/enclave/storage/enclavedb/events.go b/go/enclave/storage/enclavedb/events.go index 5bd69656b9..a5fe332eb9 100644 --- a/go/enclave/storage/enclavedb/events.go +++ b/go/enclave/storage/enclavedb/events.go @@ -18,7 +18,7 @@ import ( const ( baseEventsJoin = "from event_log e " + - "join exec_tx extx on e.exec_tx=extx.id" + + "join receipt extx on e.receipt=extx.id" + " join tx on extx.tx=tx.id " + " join batch b on extx.batch=b.sequence " + "join event_type et on e.event_type=et.id " + @@ -67,8 +67,8 @@ func WriteEventTopic(ctx context.Context, dbTX *sql.Tx, topic *gethcommon.Hash, return uint64(id), nil } -func UpdateEventTopic(ctx context.Context, dbTx *sql.Tx, etId uint64, eoaId uint64) error { - _, err := dbTx.ExecContext(ctx, "update event_topic set rel_address=? where id=?", eoaId, etId) +func UpdateEventTopicLifecycle(ctx context.Context, dbTx *sql.Tx, etId uint64, isLifecycle bool) error { + _, err := dbTx.ExecContext(ctx, "update event_topic set lifecycle_event=? where id=?", isLifecycle, etId) return err } @@ -84,7 +84,7 @@ func ReadEventTopic(ctx context.Context, dbTX *sql.Tx, topic []byte) (uint64, *u } func WriteEventLog(ctx context.Context, dbTX *sql.Tx, eventTypeId uint64, userTopics []*uint64, data []byte, logIdx uint, execTx uint64) error { - _, err := dbTX.ExecContext(ctx, "insert into event_log (event_type, topic1, topic2, topic3, datablob, log_idx, exec_tx) values (?,?,?,?,?,?,?)", + _, err := dbTX.ExecContext(ctx, "insert into event_log (event_type, topic1, topic2, topic3, datablob, log_idx, receipt) values (?,?,?,?,?,?,?)", eventTypeId, userTopics[0], userTopics[1], userTopics[2], data, logIdx, execTx) return err } diff --git a/go/enclave/storage/init/edgelessdb/001_init.sql b/go/enclave/storage/init/edgelessdb/001_init.sql index e9aecf8e38..1f356a1add 100644 --- a/go/enclave/storage/init/edgelessdb/001_init.sql +++ b/go/enclave/storage/init/edgelessdb/001_init.sql @@ -92,7 +92,7 @@ create table if not exists obsdb.tx id INTEGER AUTO_INCREMENT, hash binary(32) NOT NULL, content mediumblob NOT NULL, - sender_address int NOT NULL, + sender_address int NOT NULL, idx int NOT NULL, batch_height int NOT NULL, INDEX USING HASH (hash), @@ -102,18 +102,18 @@ create table if not exists obsdb.tx ); GRANT ALL ON obsdb.tx TO obscuro; -create table if not exists obsdb.exec_tx +create table if not exists obsdb.receipt ( id INTEGER AUTO_INCREMENT, created_contract_address int, - receipt mediumblob, + content mediumblob, tx int, batch int NOT NULL, INDEX (batch), INDEX (tx, created_contract_address), primary key (id) ); -GRANT ALL ON obsdb.exec_tx TO obscuro; +GRANT ALL ON obsdb.receipt TO obscuro; create table if not exists obsdb.contract ( @@ -165,8 +165,8 @@ create table if not exists obsdb.event_log topic3 INTEGER, datablob mediumblob, log_idx INTEGER NOT NULL, - exec_tx INTEGER NOT NULL, + receipt INTEGER NOT NULL, primary key (id), - INDEX (exec_tx, event_type, topic1, topic2, topic3) + INDEX (receipt, event_type, topic1, topic2, topic3) ); GRANT ALL ON obsdb.event_log TO obscuro; \ No newline at end of file diff --git a/go/enclave/storage/init/sqlite/001_init.sql b/go/enclave/storage/init/sqlite/001_init.sql index b22cb3a014..b26af21b69 100644 --- a/go/enclave/storage/init/sqlite/001_init.sql +++ b/go/enclave/storage/init/sqlite/001_init.sql @@ -89,17 +89,17 @@ create index IDX_TX_HASH on tx (hash); create index IDX_TX_SENDER_ADDRESS on tx (sender_address); create index IDX_TX_BATCH_HEIGHT on tx (batch_height, idx); -create table if not exists exec_tx +create table if not exists receipt ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_contract_address INTEGER REFERENCES contract, - receipt mediumblob, + content mediumblob, -- commenting out the fk until synthetic transactions are also stored tx INTEGER, batch INTEGER NOT NULL REFERENCES batch ); -create index IDX_EX_TX_BATCH on exec_tx (batch); -create index IDX_EX_TX_CCA on exec_tx (created_contract_address, tx); +create index IDX_EX_TX_BATCH on receipt (batch); +create index IDX_EX_TX_CCA on receipt (created_contract_address, tx); create table if not exists contract ( @@ -147,13 +147,13 @@ create table if not exists event_log topic3 INTEGER references event_topic, datablob mediumblob, log_idx INTEGER NOT NULL, - exec_tx INTEGER NOT NULL references exec_tx + receipt INTEGER NOT NULL references receipt ); --- create index IDX_BATCH_TX on event_log (exec_tx); -create index IDX_EV on event_log (exec_tx, event_type, topic1, topic2, topic3); +-- create index IDX_BATCH_TX on event_log (receipt); +create index IDX_EV on event_log (receipt, event_type, topic1, topic2, topic3); -- requester - address --- exec_tx - range of batch heights or a single batch +-- receipt - range of batch heights or a single batch -- address []list of contract addresses -- topic0 - event sig []list -- topic1 []list @@ -162,8 +162,8 @@ create index IDX_EV on event_log (exec_tx, event_type, topic1, topic2, topic3); -- select * from event_log --- join exec_tx on exec_tx --- join batch on exec_tx.batch -- to get the batch height range +-- join receipt on receipt +-- join batch on receipt.batch -- to get the batch height range -- join event_type ec on event_type -- join contract c on -- left join event_topic t1 on topic1 @@ -173,7 +173,7 @@ create index IDX_EV on event_log (exec_tx, event_type, topic1, topic2, topic3); -- left join event_topic t3 on topic3 -- left join externally_owned_account eoa3 on t3.rel_address -- where --- exec_tx. +-- receipt. -- c.address in [address..] AND -- ec.event_sig in [topic0..] AND -- t1.topic in [topic1..] AND diff --git a/go/enclave/storage/storage.go b/go/enclave/storage/storage.go index a16b11ceb5..6eca3c72d5 100644 --- a/go/enclave/storage/storage.go +++ b/go/enclave/storage/storage.go @@ -292,11 +292,19 @@ func (s *storageImpl) StoreBlock(ctx context.Context, block *types.Block, chainF if chainFork != nil && chainFork.IsFork() { s.logger.Info(fmt.Sprintf("Update Fork. %s", chainFork)) - err := enclavedb.UpdateCanonicalValue(ctx, dbTx, false, chainFork.NonCanonicalPath, s.logger) + err := enclavedb.UpdateCanonicalBlock(ctx, dbTx, false, chainFork.NonCanonicalPath) if err != nil { return err } - err = enclavedb.UpdateCanonicalValue(ctx, dbTx, true, chainFork.CanonicalPath, s.logger) + err = enclavedb.UpdateCanonicalBlock(ctx, dbTx, true, chainFork.CanonicalPath) + if err != nil { + return err + } + err = enclavedb.UpdateCanonicalBatch(ctx, dbTx, false, chainFork.NonCanonicalPath) + if err != nil { + return err + } + err = enclavedb.UpdateCanonicalBatch(ctx, dbTx, true, chainFork.CanonicalPath) if err != nil { return err } @@ -563,14 +571,13 @@ func (s *storageImpl) StoreBatch(ctx context.Context, batch *core.Batch, convert if err != nil { return err } - // sanity check because a batch can't be canonical if its parent is not + + // sanity check: a batch can't be canonical if its parent is not parentIsCanon, err := enclavedb.IsCanonicalBatchHash(ctx, dbTx, &batch.Header.ParentHash) if err != nil { return err } parentIsCanon = parentIsCanon || batch.SeqNo().Uint64() <= common.L2GenesisSeqNo+2 - - // sanity check that the parent is canonical if isL1ProofCanonical && !parentIsCanon { s.logger.Crit("invalid chaining. Batch is canonical. Parent is not", log.BatchHashKey, batch.Hash(), "parentHash", batch.Header.ParentHash) } @@ -586,32 +593,9 @@ func (s *storageImpl) StoreBatch(ctx context.Context, batch *core.Batch, convert // only insert transactions if this is the first time a batch of this height is created if !existsHeight { - senders := make([]*uint64, len(batch.Transactions)) - // insert the tx signers as externally owned accounts - for i, tx := range batch.Transactions { - sender, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) - if err != nil { - return fmt.Errorf("could not read tx sender. Cause: %w", err) - } - id, err := s.readEOA(ctx, dbTx, sender) - if err != nil { - if errors.Is(err, errutil.ErrNotFound) { - wid, err := enclavedb.WriteEoa(ctx, dbTx, sender) - if err != nil { - return fmt.Errorf("could not write the eoa. Cause: %w", err) - } - id = &wid - //todo - decide how to handle the corner case where events were emitted before - //etId, _, err := s.findEventTopic(ctx, dbTx, sender.Bytes()) - //if err == nil { - // err = enclavedb.UpdateEventTopic(ctx, dbTx, etId, id) - // if err != nil { - // return fmt.Errorf("could not update the event topic. Cause: %w", err) - // } - //} - } - } - senders[i] = id + senders, err := s.handleTxSenders(ctx, batch, dbTx) + if err != nil { + return err } if err := enclavedb.WriteTransactions(ctx, dbTx, batch, senders); err != nil { @@ -631,6 +615,31 @@ func (s *storageImpl) StoreBatch(ctx context.Context, batch *core.Batch, convert return nil } +func (s *storageImpl) handleTxSenders(ctx context.Context, batch *core.Batch, dbTx *sql.Tx) ([]*uint64, error) { + senders := make([]*uint64, len(batch.Transactions)) + // insert the tx signers as externally owned accounts + for i, tx := range batch.Transactions { + sender, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + if err != nil { + return nil, fmt.Errorf("could not read tx sender. Cause: %w", err) + } + eoaID, err := s.readEOA(ctx, dbTx, sender) + if err != nil { + if errors.Is(err, errutil.ErrNotFound) { + wid, err := enclavedb.WriteEoa(ctx, dbTx, sender) + if err != nil { + return nil, fmt.Errorf("could not write the eoa. Cause: %w", err) + } + eoaID = &wid + } else { + return nil, fmt.Errorf("could not insert EOA. cause: %w", err) + } + } + senders[i] = eoaID + } + return senders, nil +} + func (s *storageImpl) StoreExecutedBatch(ctx context.Context, batch *common.BatchHeader, receipts []*types.Receipt) error { defer s.logDuration("StoreExecutedBatch", measure.NewStopwatch()) executed, err := enclavedb.BatchWasExecuted(ctx, s.db.GetSQLDB(), batch.Hash()) @@ -674,36 +683,31 @@ func (s *storageImpl) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql.Tx return fmt.Errorf("could not get transaction id. Cause: %w", err) } - // store the contract.address - var createdContract *uint64 - var nilAddr gethcommon.Address - if receipt.ContractAddress != nilAddr { - createdContractId, err := s.readContractAddress(ctx, dbTX, receipt.ContractAddress) + // store the created contractaddress + var createdContractId *uint64 + if len(receipt.ContractAddress.Bytes()) > 0 { + createdContractId, err = enclavedb.WriteContractAddress(ctx, dbTX, receipt.ContractAddress, *senderId) if err != nil { - if errors.Is(err, errutil.ErrNotFound) { - createdContractId, err = enclavedb.WriteContractAddress(ctx, dbTX, receipt.ContractAddress, *senderId) - if err != nil { - return fmt.Errorf("could not write contract address. Cause: %w", err) - } - } - // return fmt.Errorf("could not read contract address. Cause: %w", err) + return fmt.Errorf("could not write contract address. Cause: %w", err) } - createdContract = createdContractId } - // Convert the receipt into their storage form and serialize them + + // Convert the receipt into its storage form and serialize + // this removes information that can be recreated + // todo - in a future iteration, this can be slimmed down further because we already store the logs separately storageReceipt := (*types.ReceiptForStorage)(receipt) receiptBytes, err := rlp.EncodeToBytes(storageReceipt) if err != nil { return fmt.Errorf("failed to encode block receipts. Cause: %w", err) } - execTxId, err := enclavedb.WriteExecutedTransaction(ctx, dbTX, batch.SequencerOrderNo.Uint64(), txId, createdContract, receiptBytes) + execTxId, err := enclavedb.WriteReceipt(ctx, dbTX, batch.SequencerOrderNo.Uint64(), txId, createdContractId, receiptBytes) if err != nil { return fmt.Errorf("could not write receipt. Cause: %w", err) } for _, l := range receipt.Logs { - err := s.storeEventLog(ctx, dbTX, execTxId, l, senderId) + err := s.storeEventLog(ctx, dbTX, execTxId, l) if err != nil { return fmt.Errorf("could not store log entry %v. Cause: %w", l, err) } @@ -711,67 +715,16 @@ func (s *storageImpl) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql.Tx return nil } -func (s *storageImpl) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTxId uint64, l *types.Log, senderId *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 - isLifecycle := true - for i := 1; i < len(l.Topics); i++ { - topic := l.Topics[i] - // first check if there is an entry already - eventTopicId, relAddressId, err := s.findEventTopic(ctx, dbTX, topic.Bytes()) - if err != nil { - if errors.Is(err, errutil.ErrNotFound) { - // check whether the topic is an EOA - relAddressId, err = s.findRelevantAddress(ctx, dbTX, topic) - if err != nil && !errors.Is(err, errutil.ErrNotFound) { - return fmt.Errorf("could not find relevant address. Cause %w", err) - } - eventTopicId, err = enclavedb.WriteEventTopic(ctx, dbTX, &topic, relAddressId) - if err != nil { - return fmt.Errorf("could not write event topic. Cause: %w", err) - } - } else { - return fmt.Errorf("could not find event topic. Cause: %w", err) - } - } - if relAddressId != nil { - isLifecycle = false - } - topicIds[i-1] = &eventTopicId +func (s *storageImpl) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTxId uint64, l *types.Log) error { + topicIds, isLifecycle, err := s.handleUserTopics(ctx, dbTX, l) + if err != nil { + return err } - // read the event type - var eventTypeId uint64 - eventT, err := s.readEventType(ctx, dbTX, l.Address, l.Topics[0]) + eventTypeId, err := s.handleEventType(ctx, dbTX, l, isLifecycle) if err != nil { - if errors.Is(err, errutil.ErrNotFound) { - contractAddId, err := s.readContractAddress(ctx, dbTX, l.Address) - if err != nil { - if errors.Is(err, errutil.ErrNotFound) { - contractAddId, err = enclavedb.WriteContractAddress(ctx, dbTX, l.Address, *senderId) - if err != nil { - return fmt.Errorf("could not write contract address. Cause: %w", err) - } - } - // return fmt.Errorf("could not read contract address. Cause: %w", err) - } - - // if not found, insert - eventTypeId, err = enclavedb.WriteEventType(ctx, dbTX, contractAddId, l.Topics[0], isLifecycle) - if err != nil { - return fmt.Errorf("could not write event type. Cause: %w", err) - } - } else { - return fmt.Errorf("could not read event type. Cause: %w", err) - } - } else { - eventTypeId = eventT.id + return err } - //if !isLifecycle && event.isLifecycle { - // todo - update event type - //} // normalize data data := l.Data @@ -786,22 +739,100 @@ func (s *storageImpl) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTxId return nil } +func (s *storageImpl) handleEventType(ctx context.Context, dbTX *sql.Tx, l *types.Log, isLifecycle bool) (uint64, error) { + et, err := s.readEventType(ctx, dbTX, l.Address, l.Topics[0]) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return 0, fmt.Errorf("could not read event type. Cause: %w", err) + } + if err == nil { + // in case we determined the current emitted event is not lifecycle, we must update the eventType + if !isLifecycle && et.isLifecycle { + err := enclavedb.UpdateEventTopicLifecycle(ctx, dbTX, et.id, isLifecycle) + if err != nil { + return 0, fmt.Errorf("could not update the event type. cause: %w", err) + } + } + return et.id, nil + } + + // the first time an event of this type is emitted we must store it + contractAddId, err := s.readContractAddress(ctx, dbTX, l.Address) + if err != nil { + // the contract was already stored when it was created + return 0, fmt.Errorf("could not read contract address. Cause: %w", err) + } + return enclavedb.WriteEventType(ctx, dbTX, contractAddId, l.Topics[0], isLifecycle) +} + +func (s *storageImpl) handleUserTopics(ctx context.Context, dbTX *sql.Tx, l *types.Log) ([]*uint64, bool, 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 + isLifecycle := true + for i := 1; i < len(l.Topics); i++ { + topic := l.Topics[i] + // first check if there is an entry already for this topic + eventTopicId, relAddressId, err := s.findEventTopic(ctx, dbTX, topic.Bytes()) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return nil, false, 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 = s.findRelevantAddress(ctx, dbTX, topic) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return nil, false, fmt.Errorf("could not read relevant address. Cause %w", err) + } + eventTopicId, err = enclavedb.WriteEventTopic(ctx, dbTX, &topic, relAddressId) + if err != nil { + return nil, false, fmt.Errorf("could not write event topic. Cause: %w", err) + } + } + + if relAddressId != nil { + isLifecycle = false + } + topicIds[i-1] = &eventTopicId + } + return topicIds, isLifecycle, 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 does not have associated code (meaning it's a smart-contract address) -// - It has a non-zero nonce (to prevent accidental or malicious creation of the address matching a given topic, -// forcing its events to become permanently private (this is not implemented for now) +// - It is not a smart contract address func (s *storageImpl) findRelevantAddress(ctx context.Context, dbTX *sql.Tx, topic gethcommon.Hash) (*uint64, error) { potentialAddr := common.ExtractPotentialAddress(topic) - if potentialAddr != nil { - eoaID, err := s.readEOA(ctx, dbTX, *potentialAddr) - if err != nil { - return nil, err - } + if potentialAddr == nil { + return nil, errutil.ErrNotFound + } + + // first check whether there is already an entry in the EOA table + eoaID, err := s.readEOA(ctx, dbTX, *potentialAddr) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return nil, err + } + if err == nil { return eoaID, nil - // todo - do we need to check anything else? } - return nil, nil + + // if the address is a contract then it's clearly not an EOA + _, err = s.readContractAddress(ctx, dbTX, *potentialAddr) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return nil, err + } + if err == nil { + 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 } func (s *storageImpl) readEventType(ctx context.Context, dbTX *sql.Tx, contractAddress gethcommon.Address, eventSignature gethcommon.Hash) (*eventType, error) {