diff --git a/go/enclave/l2chain/interfaces.go b/go/enclave/l2chain/interfaces.go index 6c29fd25a3..83eb0fee96 100644 --- a/go/enclave/l2chain/interfaces.go +++ b/go/enclave/l2chain/interfaces.go @@ -21,7 +21,7 @@ type ObscuroChain interface { // For Contracts - the address of the deployer. // Note - this might be subject to change if we implement a more flexible mechanism // todo - support BlockNumberOrHash - AccountOwner(ctx context.Context, address gethcommon.Address, blockNumber *gethrpc.BlockNumber) (*gethcommon.Address, error) + AccountOwner(ctx context.Context, address gethcommon.Address) (*gethcommon.Address, error) // GetBalanceAtBlock - will return the balance of a specific address at the specific given block number (batch number). GetBalanceAtBlock(ctx context.Context, accountAddr gethcommon.Address, blockNumber *gethrpc.BlockNumber) (*hexutil.Big, error) diff --git a/go/enclave/l2chain/l2_chain.go b/go/enclave/l2chain/l2_chain.go index 015ca2337c..39989e578c 100644 --- a/go/enclave/l2chain/l2_chain.go +++ b/go/enclave/l2chain/l2_chain.go @@ -66,33 +66,17 @@ func NewChain( } } -func (oc *obscuroChain) AccountOwner(ctx context.Context, address gethcommon.Address, blockNumber *gethrpc.BlockNumber) (*gethcommon.Address, error) { - // check if account is a contract - _, err := oc.storage.ReadContractAddress(ctx, address) +func (oc *obscuroChain) AccountOwner(ctx context.Context, address gethcommon.Address) (*gethcommon.Address, error) { + // check if the account is a contract and return the owner + owner, err := oc.storage.ReadContractOwner(ctx, address) if err != nil { + // it is not a conract, so it's an EOA if errors.Is(err, errutil.ErrNotFound) { - // the account is not a contract, so it must be an EOA return &address, nil } - return nil, err - } - - // If the address is a contract, find the signer of the deploy transaction - txHash, err := oc.storage.GetContractCreationTx(ctx, address) - if err != nil { - return nil, err - } - transaction, _, _, _, err := oc.storage.GetTransaction(ctx, *txHash) //nolint:dogsled - if err != nil { - return nil, err - } - signer := types.NewLondonSigner(oc.chainConfig.ChainID) - - sender, err := signer.Sender(transaction) - if err != nil { - return nil, err + return nil, fmt.Errorf("could not read account owner. cause: %w", err) } - return &sender, nil + return owner, nil } func (oc *obscuroChain) GetBalanceAtBlock(ctx context.Context, accountAddr gethcommon.Address, blockNumber *gethrpc.BlockNumber) (*hexutil.Big, error) { diff --git a/go/enclave/rpc/GetBalance.go b/go/enclave/rpc/GetBalance.go index 455de66851..18446a1d2a 100644 --- a/go/enclave/rpc/GetBalance.go +++ b/go/enclave/rpc/GetBalance.go @@ -43,7 +43,7 @@ func GetBalanceValidate(reqParams []any, builder *CallBuilder[BalanceReq, hexuti } func GetBalanceExecute(builder *CallBuilder[BalanceReq, hexutil.Big], rpc *EncryptionManager) error { - acctOwner, err := rpc.chain.AccountOwner(builder.ctx, *builder.Param.Addr, builder.Param.Block.BlockNumber) + acctOwner, err := rpc.chain.AccountOwner(builder.ctx, *builder.Param.Addr) if err != nil { return fmt.Errorf("cannot determine account owner. Cause: %w", err) } diff --git a/go/enclave/storage/enclavedb/batch.go b/go/enclave/storage/enclavedb/batch.go index a8b0105619..240e67adb9 100644 --- a/go/enclave/storage/enclavedb/batch.go +++ b/go/enclave/storage/enclavedb/batch.go @@ -56,7 +56,7 @@ func ExistsBatchAtHeight(ctx context.Context, dbTx *sql.Tx, height *big.Int) (bo } // WriteTransactions - persists the batch and the transactions -func WriteTransactions(ctx context.Context, dbtx *sql.Tx, batch *core.Batch) error { +func WriteTransactions(ctx context.Context, dbtx *sql.Tx, batch *core.Batch, senders []*uint64) error { // creates a batch insert statement for all entries if len(batch.Transactions) > 0 { insert := "insert into tx (hash, content, sender_address, idx, batch_height) values " + repeat("(?,?,?,?,?)", ",", len(batch.Transactions)) @@ -68,14 +68,9 @@ func WriteTransactions(ctx context.Context, dbtx *sql.Tx, batch *core.Batch) err return fmt.Errorf("failed to encode block receipts. Cause: %w", err) } - from, err := types.Sender(types.LatestSignerForChainID(transaction.ChainId()), transaction) - if err != nil { - return fmt.Errorf("unable to convert tx to message - %w", err) - } - args = append(args, transaction.Hash()) // tx_hash args = append(args, txBytes) // content - args = append(args, from.Bytes()) // sender_address + args = append(args, senders[i]) // sender_address args = append(args, i) // idx args = append(args, batch.Header.Number.Uint64()) // the batch height which contained it } @@ -129,17 +124,18 @@ func WriteExecutedTransaction(ctx context.Context, dbtx *sql.Tx, batchSeqNo uint return uint64(id), nil } -func GetTxId(ctx context.Context, dbtx *sql.Tx, txHash gethcommon.Hash) (*uint64, error) { +func ReadTransactionIdAndSender(ctx context.Context, dbtx *sql.Tx, txHash gethcommon.Hash) (*uint64, *uint64, error) { var txId uint64 - err := dbtx.QueryRowContext(ctx, "select id from tx where hash=? ", txHash.Bytes()).Scan(&txId) + var senderId uint64 + err := dbtx.QueryRowContext(ctx, "select id,sender_address from tx where hash=? ", txHash.Bytes()).Scan(&txId, &senderId) if err != nil { if errors.Is(err, sql.ErrNoRows) { // make sure the error is converted to obscuro-wide not found error - return nil, errutil.ErrNotFound + return nil, nil, errutil.ErrNotFound } - return nil, err + return nil, nil, err } - return &txId, err + return &txId, &senderId, err } func ReadBatchHeaderBySeqNo(ctx context.Context, db *sql.DB, seqNo uint64) (*common.BatchHeader, error) { diff --git a/go/enclave/storage/enclavedb/events.go b/go/enclave/storage/enclavedb/events.go index 95de777d96..8dea333f71 100644 --- a/go/enclave/storage/enclavedb/events.go +++ b/go/enclave/storage/enclavedb/events.go @@ -271,7 +271,7 @@ func loadLogs(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Add return result, nil } -func WriteEoa(ctx context.Context, dbTX *sql.Tx, sender *gethcommon.Address) (uint64, error) { +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()) if err != nil { @@ -300,9 +300,9 @@ func ReadEoa(ctx context.Context, dbTx *sql.Tx, addr gethcommon.Address) (uint64 return id, nil } -func WriteContractAddress(ctx context.Context, dbTX *sql.Tx, contractAddress *gethcommon.Address) (*uint64, error) { - insert := "insert into contract (address) values (?)" - res, err := dbTX.ExecContext(ctx, insert, contractAddress.Bytes()) +func WriteContractAddress(ctx context.Context, dbTX *sql.Tx, contractAddress gethcommon.Address, ownerAddress uint64) (*uint64, error) { + insert := "insert into contract (address, owner) values (?,?)" + res, err := dbTX.ExecContext(ctx, insert, contractAddress.Bytes(), ownerAddress) if err != nil { return nil, err } @@ -314,7 +314,7 @@ func WriteContractAddress(ctx context.Context, dbTX *sql.Tx, contractAddress *ge return &v, nil } -func ReadContractAddress(ctx context.Context, dbTx *sql.Tx, addr gethcommon.Address) (uint64, error) { +func ReadContractAddress(ctx context.Context, dbTx *sql.Tx, addr gethcommon.Address) (*uint64, error) { row := dbTx.QueryRowContext(ctx, "select id from contract where address = ?", addr.Bytes()) var id uint64 @@ -322,12 +322,28 @@ func ReadContractAddress(ctx context.Context, dbTx *sql.Tx, addr gethcommon.Addr if err != nil { if errors.Is(err, sql.ErrNoRows) { // make sure the error is converted to obscuro-wide not found error - return 0, errutil.ErrNotFound + return nil, errutil.ErrNotFound } - return 0, err + return nil, err } - return id, nil + return &id, nil +} + +func ReadContractOwner(ctx context.Context, db *sql.DB, address gethcommon.Address) (*gethcommon.Address, error) { + row := db.QueryRowContext(ctx, "select eoa.address from contract c join externally_owned_account eoa on c.owner=eoa.id where c.address = ?", address.Bytes()) + + var eoaAddress gethcommon.Address + err := row.Scan(&eoaAddress) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // make sure the error is converted to obscuro-wide not found error + return nil, errutil.ErrNotFound + } + return nil, err + } + + return &eoaAddress, nil } func stringToHash(ns sql.NullString) gethcommon.Hash { diff --git a/go/enclave/storage/init/edgelessdb/001_init.sql b/go/enclave/storage/init/edgelessdb/001_init.sql index 39a37fbe39..ae4e9b705c 100644 --- a/go/enclave/storage/init/edgelessdb/001_init.sql +++ b/go/enclave/storage/init/edgelessdb/001_init.sql @@ -92,11 +92,11 @@ create table if not exists obsdb.tx id INTEGER AUTO_INCREMENT, hash binary(32) NOT NULL, content mediumblob NOT NULL, - sender_address binary(20) NOT NULL, + sender_address int NOT NULL, idx int NOT NULL, batch_height int NOT NULL, INDEX USING HASH (hash), - INDEX USING HASH (sender_address), + INDEX (sender_address), INDEX (batch_height, idx), primary key (id) ); @@ -119,6 +119,7 @@ create table if not exists obsdb.contract ( id INTEGER AUTO_INCREMENT, address binary(20) NOT NULL, + owner int NOT NULL, primary key (id), INDEX USING HASH (address) ); diff --git a/go/enclave/storage/init/sqlite/001_init.sql b/go/enclave/storage/init/sqlite/001_init.sql index d924e88ff6..b22cb3a014 100644 --- a/go/enclave/storage/init/sqlite/001_init.sql +++ b/go/enclave/storage/init/sqlite/001_init.sql @@ -81,7 +81,7 @@ create table if not exists tx id INTEGER PRIMARY KEY AUTOINCREMENT, hash binary(32) NOT NULL, content mediumblob NOT NULL, - sender_address binary(20) NOT NULL, + sender_address int NOT NULL REFERENCES externally_owned_account, idx int NOT NULL, batch_height int NOT NULL ); @@ -104,9 +104,11 @@ create index IDX_EX_TX_CCA on exec_tx (created_contract_address, tx); create table if not exists contract ( id INTEGER PRIMARY KEY AUTOINCREMENT, - address binary(20) NOT NULL + address binary(20) NOT NULL, +-- denormalised for ease of access during balance checks + owner int NOT NULL REFERENCES externally_owned_account ); -create index IDX_CONTRACT_AD on contract (address); +create index IDX_CONTRACT_AD on contract (address, owner); create table if not exists externally_owned_account ( diff --git a/go/enclave/storage/interfaces.go b/go/enclave/storage/interfaces.go index 8e67296ce1..80c817a87d 100644 --- a/go/enclave/storage/interfaces.go +++ b/go/enclave/storage/interfaces.go @@ -155,6 +155,7 @@ type Storage interface { ReadEOA(ctx context.Context, addr gethcommon.Address) (*uint64, error) ReadContractAddress(ctx context.Context, addr gethcommon.Address) (*uint64, error) + ReadContractOwner(ctx context.Context, address gethcommon.Address) (*gethcommon.Address, error) } type ScanStorage interface { diff --git a/go/enclave/storage/storage.go b/go/enclave/storage/storage.go index e470c87430..85f919ea58 100644 --- a/go/enclave/storage/storage.go +++ b/go/enclave/storage/storage.go @@ -462,6 +462,7 @@ func (s *storageImpl) GetTransaction(ctx context.Context, txHash gethcommon.Hash return enclavedb.ReadTransaction(ctx, s.db.GetSQLDB(), txHash) } +// todo - get rid func (s *storageImpl) GetContractCreationTx(ctx context.Context, address gethcommon.Address) (*gethcommon.Hash, error) { defer s.logDuration("GetContractCreationTx", measure.NewStopwatch()) return enclavedb.GetContractCreationTx(ctx, s.db.GetSQLDB(), address) @@ -589,27 +590,22 @@ func (s *storageImpl) StoreBatch(ctx context.Context, batch *core.Batch, convert return fmt.Errorf("could not write batch header. Cause: %w", err) } - // only insert transactions if this is the first time a batch of this height is created - if !existsHeight { - if err := enclavedb.WriteTransactions(ctx, dbTx, batch); err != nil { - return fmt.Errorf("could not write transactions. Cause: %w", err) - } - } - + senders := make([]*uint64, len(batch.Transactions)) // insert the tx signers as externally owned accounts - for _, tx := range batch.Transactions { - sender, err := core.GetAuthenticatedSender(s.chainConfig.ChainID.Int64(), tx) + 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) } - _, err = s.readEOA(ctx, dbTx, *sender) + id, err := s.readEOA(ctx, dbTx, sender) if err != nil { if errors.Is(err, errutil.ErrNotFound) { - _, err := enclavedb.WriteEoa(ctx, dbTx, sender) + wid, err := enclavedb.WriteEoa(ctx, dbTx, sender) if err != nil { return fmt.Errorf("could not write the eoa. Cause: %w", err) } - //todo + 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) @@ -619,6 +615,14 @@ func (s *storageImpl) StoreBatch(ctx context.Context, batch *core.Batch, convert //} } } + senders[i] = id + } + + // only insert transactions if this is the first time a batch of this height is created + if !existsHeight { + if err := enclavedb.WriteTransactions(ctx, dbTx, batch, senders); err != nil { + return fmt.Errorf("could not write transactions. Cause: %w", err) + } } if err := dbTx.Commit(); err != nil { @@ -671,6 +675,11 @@ func (s *storageImpl) StoreExecutedBatch(ctx context.Context, batch *common.Batc // todo - move this to a separate service func (s *storageImpl) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql.Tx, batch *common.BatchHeader, receipt *types.Receipt) error { + txId, senderId, err := enclavedb.ReadTransactionIdAndSender(ctx, dbTX, receipt.TxHash) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return fmt.Errorf("could not get transaction id. Cause: %w", err) + } + // store the contract.address var createdContract *uint64 var nilAddr gethcommon.Address @@ -678,7 +687,7 @@ func (s *storageImpl) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql.Tx createdContractId, err := s.readContractAddress(ctx, dbTX, receipt.ContractAddress) if err != nil { if errors.Is(err, errutil.ErrNotFound) { - createdContractId, err = enclavedb.WriteContractAddress(ctx, dbTX, &receipt.ContractAddress) + createdContractId, err = enclavedb.WriteContractAddress(ctx, dbTX, receipt.ContractAddress, *senderId) if err != nil { return fmt.Errorf("could not write contract address. Cause: %w", err) } @@ -694,18 +703,13 @@ func (s *storageImpl) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql.Tx return fmt.Errorf("failed to encode block receipts. Cause: %w", err) } - txId, err := enclavedb.GetTxId(ctx, dbTX, receipt.TxHash) - if err != nil && !errors.Is(err, errutil.ErrNotFound) { - return fmt.Errorf("could not get transaction id. Cause: %w", err) - } - execTxId, err := enclavedb.WriteExecutedTransaction(ctx, dbTX, batch.SequencerOrderNo.Uint64(), txId, createdContract, 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) + err := s.storeEventLog(ctx, dbTX, execTxId, l, senderId) if err != nil { return fmt.Errorf("could not store log entry %v. Cause: %w", l, err) } @@ -713,7 +717,7 @@ 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) error { +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 @@ -752,7 +756,7 @@ func (s *storageImpl) storeEventLog(ctx context.Context, dbTX *sql.Tx, execTxId 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) + contractAddId, err = enclavedb.WriteContractAddress(ctx, dbTX, l.Address, *senderId) if err != nil { return fmt.Errorf("could not write contract address. Cause: %w", err) } @@ -817,7 +821,7 @@ func (s *storageImpl) readEventType(ctx context.Context, dbTX *sql.Tx, contractA if err != nil { return nil, err } - id, isLifecycle, err := enclavedb.ReadEventType(ctx, dbTX, contractAddrId, eventSignature) + id, isLifecycle, err := enclavedb.ReadEventType(ctx, dbTX, *contractAddrId, eventSignature) if err != nil { return nil, err } @@ -1021,6 +1025,10 @@ func (s *storageImpl) ReadContractAddress(ctx context.Context, addr gethcommon.A return s.readContractAddress(ctx, dbtx, addr) } +func (s *storageImpl) ReadContractOwner(ctx context.Context, address gethcommon.Address) (*gethcommon.Address, error) { + return enclavedb.ReadContractOwner(ctx, s.db.GetSQLDB(), address) +} + func (s *storageImpl) readContractAddress(ctx context.Context, dbTX *sql.Tx, addr gethcommon.Address) (*uint64, error) { defer s.logDuration("readContractAddress", measure.NewStopwatch()) id, err := common.GetCachedValue(ctx, s.contractAddressCache, s.logger, addr, func(v any) (*uint64, error) { @@ -1028,7 +1036,7 @@ func (s *storageImpl) readContractAddress(ctx context.Context, dbTX *sql.Tx, add if err != nil { return nil, err } - return &id, nil + return id, nil }) if err != nil { return nil, err