From c058c64368c72065e5b43be7c4fd1ca89193d897 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Wed, 16 Oct 2024 10:20:53 +0100 Subject: [PATCH] Rework tx receipt storage logic (#2085) * rework tx receipt storage logic * fix personal transactions * fix merge * rename * fix * add missing receipt fields * fix * fix * fix * fix * fix * fix * fix * fix * fix --- go/enclave/components/batch_executor.go | 5 + go/enclave/core/event_types.go | 89 ++++++ go/enclave/core/utils.go | 4 +- go/enclave/evm/evm_facade.go | 8 +- go/enclave/l2chain/l2_chain.go | 2 +- go/enclave/rpc/EstimateGas.go | 2 +- go/enclave/rpc/GetPersonalTransactions.go | 11 +- go/enclave/rpc/GetTransaction.go | 9 +- go/enclave/rpc/GetTransactionReceipt.go | 93 +----- go/enclave/storage/enclavedb/batch.go | 121 +++----- go/enclave/storage/enclavedb/events.go | 268 +++++++++++++++--- go/enclave/storage/events_storage.go | 18 +- .../storage/init/edgelessdb/001_init.sql | 15 +- go/enclave/storage/init/sqlite/001_init.sql | 17 +- go/enclave/storage/interfaces.go | 5 +- go/enclave/storage/storage.go | 47 ++- go/enclave/system/contracts.go | 2 +- go/enclave/system/hooks.go | 6 +- .../simulation/p2p/in_mem_obscuro_client.go | 1 + integration/tengateway/tengateway_test.go | 16 ++ 20 files changed, 466 insertions(+), 273 deletions(-) diff --git a/go/enclave/components/batch_executor.go b/go/enclave/components/batch_executor.go index 515812218b..528b61effc 100644 --- a/go/enclave/components/batch_executor.go +++ b/go/enclave/components/batch_executor.go @@ -206,6 +206,11 @@ func (executor *batchExecutor) ComputeBatch(ctx context.Context, context *BatchE for _, txResult := range txResults { txReceipts = append(txReceipts, txResult.Receipt) } + // populate the derived fields in the receipt + err = txReceipts.DeriveFields(executor.chainConfig, batch.Hash(), batch.NumberU64(), batch.Header.Time, batch.Header.BaseFee, nil, successfulTxs) + if err != nil { + return nil, fmt.Errorf("could not derive receipts. Cause: %w", err) + } onBlockTx, err := executor.systemContracts.CreateOnBatchEndTransaction(ctx, stateDB, successfulTxs, txReceipts) if err != nil && !errors.Is(err, system.ErrNoTransactions) { diff --git a/go/enclave/core/event_types.go b/go/enclave/core/event_types.go index 92747e295c..87fe389808 100644 --- a/go/enclave/core/event_types.go +++ b/go/enclave/core/event_types.go @@ -1,6 +1,10 @@ package core import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -28,7 +32,92 @@ type ContractVisibilityConfig struct { type TxExecResult struct { Receipt *types.Receipt CreatedContracts map[gethcommon.Address]*ContractVisibilityConfig + Tx *types.Transaction Err error } +// InternalReceipt - Equivalent to the geth types.Receipt, but without weird quirks +type InternalReceipt struct { + PostState []byte + Status uint64 + CumulativeGasUsed uint64 + EffectiveGasPrice *uint64 + CreatedContract *gethcommon.Address + TxContent []byte + TxHash gethcommon.Hash + BlockHash gethcommon.Hash + BlockNumber *big.Int + TransactionIndex uint + From gethcommon.Address + To *gethcommon.Address + TxType uint8 + Logs []*types.Log +} + +// MarshalToJson marshals a transaction receipt into a JSON object. +// taken from geth +func (receipt *InternalReceipt) MarshalToJson() map[string]interface{} { + var effGasPrice *hexutil.Big + if receipt.EffectiveGasPrice != nil { + effGasPrice = (*hexutil.Big)(big.NewInt(int64(*receipt.EffectiveGasPrice))) + } + + fields := map[string]interface{}{ + "blockHash": receipt.BlockHash, + "blockNumber": hexutil.Uint64(receipt.BlockNumber.Uint64()), + "transactionHash": receipt.TxHash, + "transactionIndex": hexutil.Uint64(receipt.TransactionIndex), + "from": receipt.From, + "to": receipt.To, + "gasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), + "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), + "contractAddress": receipt.CreatedContract, + "logs": receipt.Logs, + "logsBloom": types.Bloom{}, + "type": hexutil.Uint(receipt.TxType), + "effectiveGasPrice": effGasPrice, + } + + // Assign receipt status or post state. + if len(receipt.PostState) > 0 { + fields["root"] = hexutil.Bytes(receipt.PostState) + } else { + fields["status"] = hexutil.Uint(receipt.Status) + } + if receipt.Logs == nil { + fields["logs"] = []*types.Log{} + } + + return fields +} + +func (receipt *InternalReceipt) ToReceipt() *types.Receipt { + var effGasPrice *big.Int + if receipt.EffectiveGasPrice != nil { + effGasPrice = big.NewInt(int64(*receipt.EffectiveGasPrice)) + } + + var cc gethcommon.Address + if receipt.CreatedContract != nil { + cc = *receipt.CreatedContract + } + return &types.Receipt{ + Type: receipt.TxType, + PostState: receipt.PostState, + Status: receipt.Status, + CumulativeGasUsed: receipt.CumulativeGasUsed, + Bloom: types.Bloom{}, + Logs: receipt.Logs, + TxHash: receipt.TxHash, + ContractAddress: cc, + GasUsed: receipt.CumulativeGasUsed, + EffectiveGasPrice: effGasPrice, + BlobGasUsed: 0, + BlobGasPrice: nil, + BlockHash: receipt.BlockHash, + BlockNumber: receipt.BlockNumber, + TransactionIndex: receipt.TransactionIndex, + } +} + type TxExecResults []*TxExecResult diff --git a/go/enclave/core/utils.go b/go/enclave/core/utils.go index 4140512351..1cfedf109d 100644 --- a/go/enclave/core/utils.go +++ b/go/enclave/core/utils.go @@ -15,14 +15,14 @@ import ( // VerifySignature - Checks that the L2Tx has a valid signature. func VerifySignature(chainID int64, tx *types.Transaction) error { - signer := types.NewLondonSigner(big.NewInt(chainID)) + signer := types.LatestSignerForChainID(big.NewInt(chainID)) _, err := types.Sender(signer, tx) return err } // GetAuthenticatedSender - Get sender and tx nonce from transaction func GetAuthenticatedSender(chainID int64, tx *types.Transaction) (*gethcommon.Address, error) { - signer := types.NewLondonSigner(big.NewInt(chainID)) + signer := types.LatestSignerForChainID(tx.ChainId()) sender, err := types.Sender(signer, tx) if err != nil { return nil, err diff --git a/go/enclave/evm/evm_facade.go b/go/enclave/evm/evm_facade.go index 7b75107771..380b8f798a 100644 --- a/go/enclave/evm/evm_facade.go +++ b/go/enclave/evm/evm_facade.go @@ -133,9 +133,9 @@ func executeTransaction( ) *core.TxExecResult { var createdContracts []*gethcommon.Address rules := cc.Rules(big.NewInt(0), true, 0) - from, err := types.Sender(types.LatestSigner(cc), t.Tx) + from, err := core.GetTxSigner(t.Tx) if err != nil { - return &core.TxExecResult{Err: err} + return &core.TxExecResult{Tx: t.Tx, Err: err} } s.Prepare(rules, from, gethcommon.Address{}, t.Tx.To(), nil, nil) snap := s.Snapshot() @@ -245,7 +245,7 @@ func executeTransaction( header.MixDigest = before if err != nil { s.RevertToSnapshot(snap) - return &core.TxExecResult{Receipt: receipt, Err: err} + return &core.TxExecResult{Receipt: receipt, Tx: t.Tx, Err: err} } contractsWithVisibility := make(map[gethcommon.Address]*core.ContractVisibilityConfig) @@ -253,7 +253,7 @@ func executeTransaction( contractsWithVisibility[*contractAddress] = readVisibilityConfig(vmenv, contractAddress) } - return &core.TxExecResult{Receipt: receipt, CreatedContracts: contractsWithVisibility} + return &core.TxExecResult{Receipt: receipt, Tx: t.Tx, CreatedContracts: contractsWithVisibility} } const ( diff --git a/go/enclave/l2chain/l2_chain.go b/go/enclave/l2chain/l2_chain.go index 325266bfec..a2474f5237 100644 --- a/go/enclave/l2chain/l2_chain.go +++ b/go/enclave/l2chain/l2_chain.go @@ -110,7 +110,7 @@ func (oc *obscuroChain) ObsCallAtBlock(ctx context.Context, apiArgs *gethapi.Tra } if oc.logger.Enabled(context.Background(), gethlog.LevelTrace) { - oc.logger.Trace("Obs_Call: Successful result", "result", fmt.Sprintf("contractAddress=%s, from=%s, data=%s, batch=%s, state=%s", + oc.logger.Trace("Obs_Call: Successful result", "result", fmt.Sprintf("to=%s, from=%s, data=%s, batch=%s, state=%s", callMsg.To, callMsg.From, hexutils.BytesToHex(callMsg.Data), diff --git a/go/enclave/rpc/EstimateGas.go b/go/enclave/rpc/EstimateGas.go index b288607c04..cbe47ec620 100644 --- a/go/enclave/rpc/EstimateGas.go +++ b/go/enclave/rpc/EstimateGas.go @@ -199,7 +199,7 @@ func (rpc *EncryptionManager) doEstimateGas(ctx context.Context, args *gethapi.T } cap = hi //nolint: revive isFailedAtMax, _, err := rpc.isGasEnough(ctx, args, hi, blkNumber) - //TODO: Workaround for the weird conensus nil statement down, which gets interwined with evm errors. + // TODO: Workaround for the weird conensus nil statement down, which gets interwined with evm errors. // Here if there is a consensus error - we'd bail. If the tx fails at max gas - we'd bail (probably bad) if err != nil { return 0, gethcommon.Big0, err diff --git a/go/enclave/rpc/GetPersonalTransactions.go b/go/enclave/rpc/GetPersonalTransactions.go index 309c52eda3..f712dc256d 100644 --- a/go/enclave/rpc/GetPersonalTransactions.go +++ b/go/enclave/rpc/GetPersonalTransactions.go @@ -3,6 +3,8 @@ package rpc import ( "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/gethencoding" ) @@ -32,18 +34,23 @@ func GetPersonalTransactionsExecute(builder *CallBuilder[common.ListPrivateTrans return nil //nolint:nilerr } addr := builder.Param.Address - encryptReceipts, err := rpc.storage.GetTransactionsPerAddress(builder.ctx, &addr, &builder.Param.Pagination) + internalReceipts, err := rpc.storage.GetTransactionsPerAddress(builder.ctx, &addr, &builder.Param.Pagination) if err != nil { return fmt.Errorf("GetTransactionsPerAddress - %w", err) } + var receipts types.Receipts + for _, receipt := range internalReceipts { + receipts = append(receipts, receipt.ToReceipt()) + } + receiptsCount, err := rpc.storage.CountTransactionsPerAddress(builder.ctx, &addr) if err != nil { return fmt.Errorf("CountTransactionsPerAddress - %w", err) } builder.ReturnValue = &common.PrivateTransactionsQueryResponse{ - Receipts: encryptReceipts, + Receipts: receipts, Total: receiptsCount, } return nil diff --git a/go/enclave/rpc/GetTransaction.go b/go/enclave/rpc/GetTransaction.go index 6690b99efd..b0e15bd8f5 100644 --- a/go/enclave/rpc/GetTransaction.go +++ b/go/enclave/rpc/GetTransaction.go @@ -53,11 +53,7 @@ func GetTransactionExecute(builder *CallBuilder[gethcommon.Hash, RpcTransaction] return nil } - // Unlike in the Geth impl, we hardcode the use of a London signer. - // todo (#1553) - once the enclave's genesis.json is set, retrieve the signer type using `types.MakeSigner` - signer := types.NewLondonSigner(tx.ChainId()) - rpcTx := newRPCTransaction(tx, blockHash, blockNumber, index, rpc.config.BaseFee, signer) - builder.ReturnValue = rpcTx + builder.ReturnValue = newRPCTransaction(tx, blockHash, blockNumber, index, rpc.config.BaseFee, sender) return nil } @@ -85,8 +81,7 @@ type RpcTransaction struct { //nolint } // Lifted from Geth's internal `ethapi` package. -func newRPCTransaction(tx *types.Transaction, blockHash gethcommon.Hash, blockNumber uint64, index uint64, baseFee *big.Int, signer types.Signer) *RpcTransaction { - from, _ := types.Sender(signer, tx) +func newRPCTransaction(tx *types.Transaction, blockHash gethcommon.Hash, blockNumber uint64, index uint64, baseFee *big.Int, from gethcommon.Address) *RpcTransaction { v, r, s := tx.RawSignatureValues() result := &RpcTransaction{ Type: hexutil.Uint64(tx.Type()), diff --git a/go/enclave/rpc/GetTransactionReceipt.go b/go/enclave/rpc/GetTransactionReceipt.go index 88c55f967d..0eba4fbbb1 100644 --- a/go/enclave/rpc/GetTransactionReceipt.go +++ b/go/enclave/rpc/GetTransactionReceipt.go @@ -3,18 +3,10 @@ package rpc import ( "errors" "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ten-protocol/go-ten/go/enclave/evm/ethchainadapter" - - "github.com/ten-protocol/go-ten/go/enclave/core" gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ten-protocol/go-ten/go/enclave/events" ) func GetTransactionReceiptValidate(reqParams []any, builder *CallBuilder[gethcommon.Hash, map[string]interface{}], _ *EncryptionManager) error { @@ -36,97 +28,32 @@ func GetTransactionReceiptValidate(reqParams []any, builder *CallBuilder[gethcom func GetTransactionReceiptExecute(builder *CallBuilder[gethcommon.Hash, map[string]interface{}], rpc *EncryptionManager) error { txHash := *builder.Param - // todo - optimise these calls. This can be done with a single sql - rpc.logger.Trace("Get receipt for ", log.TxKey, txHash) - // We retrieve the transaction. - tx, blockHash, number, txIndex, err := rpc.storage.GetTransaction(builder.ctx, txHash) //nolint:dogsled - if err != nil { - rpc.logger.Trace("error getting tx ", log.TxKey, txHash, log.ErrKey, err) - if errors.Is(err, errutil.ErrNotFound) { - builder.Status = NotFound - return nil - } - return err - } + requester := builder.VK.AccountAddress + rpc.logger.Trace("Get receipt for ", log.TxKey, txHash, "requester", requester.Hex()) - // We retrieve the txSigner's address. - txSigner, err := core.GetTxSigner(tx) + exists, err := rpc.storage.ExistsTransactionReceipt(builder.ctx, txHash) if err != nil { - builder.Err = err - return nil //nolint:nilerr + return fmt.Errorf("could not retrieve transaction receipt in eth_getTransactionReceipt request. Cause: %w", err) } - - if txSigner.Hex() != builder.VK.AccountAddress.Hex() { - builder.Status = NotAuthorised + if !exists { + builder.Status = NotFound return nil } // We retrieve the transaction receipt. - txReceipt, err := rpc.storage.GetTransactionReceipt(builder.ctx, txHash) + receipt, err := rpc.storage.GetTransactionReceipt(builder.ctx, txHash, requester, false) if err != nil { rpc.logger.Trace("error getting tx receipt", log.TxKey, txHash, log.ErrKey, err) if errors.Is(err, errutil.ErrNotFound) { - builder.Status = NotFound + builder.Status = NotAuthorised return nil } // this is a system error return fmt.Errorf("could not retrieve transaction receipt in eth_getTransactionReceipt request. Cause: %w", err) } - // We filter out irrelevant logs. - txReceipt.Logs, err = events.FilterLogsForReceipt(builder.ctx, txReceipt, &txSigner, rpc.registry) - if err != nil { - rpc.logger.Error("error filter logs ", log.TxKey, txHash, log.ErrKey, err) - // this is a system error - return err - } - - rpc.logger.Trace("Successfully retrieved receipt for ", log.TxKey, txHash, "rec", txReceipt) - signer := types.MakeSigner(ethchainadapter.ChainParams(big.NewInt(rpc.config.ObscuroChainID)), big.NewInt(int64(number)), 0) - r := marshalReceipt(txReceipt, blockHash, number, signer, tx, int(txIndex)) + rpc.logger.Trace("Successfully retrieved receipt for ", log.TxKey, txHash, "rec", receipt) + r := receipt.MarshalToJson() builder.ReturnValue = &r return nil } - -// marshalReceipt marshals a transaction receipt into a JSON object. -// taken from geth -func marshalReceipt(receipt *types.Receipt, blockHash gethcommon.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { - from, _ := types.Sender(signer, tx) - - fields := map[string]interface{}{ - "blockHash": blockHash, - "blockNumber": hexutil.Uint64(blockNumber), - "transactionHash": tx.Hash(), - "transactionIndex": hexutil.Uint64(txIndex), - "from": from, - "to": tx.To(), - "gasUsed": hexutil.Uint64(receipt.GasUsed), - "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), - "contractAddress": nil, - "logs": receipt.Logs, - "logsBloom": receipt.Bloom, - "type": hexutil.Uint(tx.Type()), - "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), - } - - // Assign receipt status or post state. - if len(receipt.PostState) > 0 { - fields["root"] = hexutil.Bytes(receipt.PostState) - } else { - fields["status"] = hexutil.Uint(receipt.Status) - } - if receipt.Logs == nil { - fields["logs"] = []*types.Log{} - } - - if tx.Type() == types.BlobTxType { - fields["blobGasUsed"] = hexutil.Uint64(receipt.BlobGasUsed) - fields["blobGasPrice"] = (*hexutil.Big)(receipt.BlobGasPrice) - } - - // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation - if receipt.ContractAddress != (gethcommon.Address{}) { - fields["contractAddress"] = receipt.ContractAddress - } - return fields -} diff --git a/go/enclave/storage/enclavedb/batch.go b/go/enclave/storage/enclavedb/batch.go index a3ed53c042..a66be299f5 100644 --- a/go/enclave/storage/enclavedb/batch.go +++ b/go/enclave/storage/enclavedb/batch.go @@ -7,8 +7,6 @@ import ( "fmt" "math/big" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/core/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -18,10 +16,6 @@ import ( "github.com/ten-protocol/go-ten/go/enclave/core" ) -const ( - 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 { header, err := rlp.EncodeToBytes(batch.Header) if err != nil { @@ -68,10 +62,10 @@ 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, senders []*uint64) error { +func WriteTransactions(ctx context.Context, dbtx *sql.Tx, batch *core.Batch, senders []uint64, toContracts []*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)) + insert := "insert into tx (hash, content, to_address, type, sender_address, idx, batch_height) values " + repeat("(?,?,?,?,?,?,?)", ",", len(batch.Transactions)) args := make([]any, 0) for i, transaction := range batch.Transactions { @@ -82,6 +76,8 @@ func WriteTransactions(ctx context.Context, dbtx *sql.Tx, batch *core.Batch, sen args = append(args, transaction.Hash()) // tx_hash args = append(args, txBytes) // content + args = append(args, toContracts[i]) // To + args = append(args, transaction.Type()) // Type 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 @@ -123,9 +119,17 @@ func MarkBatchExecuted(ctx context.Context, dbtx *sql.Tx, seqNo *big.Int) error return err } -func WriteReceipt(ctx context.Context, dbtx *sql.Tx, batchSeqNo uint64, txId *uint64, receipt []byte) (uint64, error) { - insert := "insert into receipt (content, tx, batch) values " + "(?,?,?)" - res, err := dbtx.ExecContext(ctx, insert, receipt, txId, batchSeqNo) +func WriteReceipt(ctx context.Context, dbtx *sql.Tx, batchSeqNo uint64, txId *uint64, receipt *types.Receipt) (uint64, error) { + insert := "insert into receipt (post_state, status, cumulative_gas_used, effective_gas_price, created_contract_address, tx, batch) values " + "(?,?,?,?,?,?,?)" + addr := &receipt.ContractAddress + if *addr == (gethcommon.Address{}) { + addr = nil + } + var effPrice uint64 + if receipt.EffectiveGasPrice != nil { + effPrice = receipt.EffectiveGasPrice.Uint64() + } + res, err := dbtx.ExecContext(ctx, insert, receipt.PostState, receipt.Status, receipt.CumulativeGasUsed, effPrice, addr, txId, batchSeqNo) if err != nil { return 0, err } @@ -251,89 +255,24 @@ func fetchBatches(ctx context.Context, db *sql.DB, whereQuery string, args ...an return result, nil } -func selectReceipts(ctx context.Context, db *sql.DB, config *params.ChainConfig, query string, args ...any) (types.Receipts, error) { - var allReceipts types.Receipts - - // where batch=? - rows, err := db.QueryContext(ctx, queryReceipts+" "+query, args...) +func ReadReceipt(ctx context.Context, db *sql.DB, txHash common.L2TxHash, requester *gethcommon.Address) (*core.InternalReceipt, error) { + rec, _, err := loadReceiptsAndEventLogs(ctx, db, requester, " AND curr_tx.hash=?", []any{txHash.Bytes()}, true) 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 } - defer rows.Close() - for rows.Next() { - // receipt, tx, batch, height - var receiptData []byte - var txData []byte - var batchHash []byte - var height uint64 - err := rows.Scan(&receiptData, &txData, &batchHash, &height) - if err != nil { - return nil, err - } - tx := new(common.L2Tx) - if err := rlp.DecodeBytes(txData, tx); err != nil { - return nil, fmt.Errorf("could not decode L2 transaction. Cause: %w", err) - } - transactions := []*common.L2Tx{tx} - - storageReceipt := new(types.ReceiptForStorage) - if err := rlp.DecodeBytes(receiptData, storageReceipt); err != nil { - return nil, fmt.Errorf("unable to decode receipt. Cause : %w", err) - } - receipts := (types.Receipts)([]*types.Receipt{(*types.Receipt)(storageReceipt)}) - - hash := common.L2BatchHash{} - hash.SetBytes(batchHash) - if err = receipts.DeriveFields(config, hash, height, 0, big.NewInt(0), big.NewInt(0), transactions); err != nil { - return nil, fmt.Errorf("failed to derive block receipts fields. hash = %s; number = %d; err = %w", hash, height, err) - } - allReceipts = append(allReceipts, receipts[0]) - } - if rows.Err() != nil { - return nil, rows.Err() - } - - return allReceipts, nil + // there should be only a single receipt + return rec[0], nil } -func ReadReceipt(ctx context.Context, db *sql.DB, txHash common.L2TxHash, config *params.ChainConfig) (*types.Receipt, error) { - 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 - var batchHash []byte - var height uint64 - err := row.Scan(&receiptData, &txData, &batchHash, &height) +func ExistsReceipt(ctx context.Context, db *sql.DB, txHash common.L2TxHash) (bool, error) { + query := "select count(1) from receipt rec join tx curr_tx on rec.tx=curr_tx.id where curr_tx.hash=?" + row := db.QueryRowContext(ctx, query, txHash.Bytes()) + var cnt uint + err := row.Scan(&cnt) 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 - } - tx := new(common.L2Tx) - if err := rlp.DecodeBytes(txData, tx); err != nil { - return nil, fmt.Errorf("could not decode L2 transaction. Cause: %w", err) - } - transactions := []*common.L2Tx{tx} - - storageReceipt := new(types.ReceiptForStorage) - if err := rlp.DecodeBytes(receiptData, storageReceipt); err != nil { - return nil, fmt.Errorf("unable to decode receipt. Cause : %w", err) - } - receipts := (types.Receipts)([]*types.Receipt{(*types.Receipt)(storageReceipt)}) - - batchhash := common.L2BatchHash{} - batchhash.SetBytes(batchHash) - // todo base fee - if err = receipts.DeriveFields(config, batchhash, height, 0, big.NewInt(1), big.NewInt(0), transactions); err != nil { - return nil, fmt.Errorf("failed to derive block receipts fields. txHash = %s; number = %d; err = %w", txHash, height, err) + return false, err } - return receipts[0], nil + return cnt > 0, nil } func ReadTransaction(ctx context.Context, db *sql.DB, txHash gethcommon.Hash) (*types.Transaction, common.L2BatchHash, uint64, uint64, error) { @@ -427,8 +366,12 @@ func BatchWasExecuted(ctx context.Context, db *sql.DB, hash common.L2BatchHash) return result, nil } -func GetTransactionsPerAddress(ctx context.Context, db *sql.DB, config *params.ChainConfig, address *gethcommon.Address, pagination *common.QueryPagination) (types.Receipts, error) { - return selectReceipts(ctx, db, config, "join externally_owned_account eoa on tx.sender_address = eoa.id where eoa.address = ? ORDER BY height DESC LIMIT ? OFFSET ? ", address.Bytes(), pagination.Size, pagination.Offset) +func GetTransactionsPerAddress(ctx context.Context, db *sql.DB, address *gethcommon.Address, pagination *common.QueryPagination) ([]*core.InternalReceipt, error) { + receipts, err := loadReceiptList(ctx, db, address, " AND tx_sender.address = ? ", []any{address.Bytes()}, " ORDER BY b.height DESC LIMIT ? OFFSET ?", []any{pagination.Size, pagination.Offset}) + if err != nil { + return nil, err + } + return receipts, nil } func CountTransactionsPerAddress(ctx context.Context, db *sql.DB, address *gethcommon.Address) (uint64, error) { diff --git a/go/enclave/storage/enclavedb/events.go b/go/enclave/storage/enclavedb/events.go index 173dd75cde..a2a1fbfee4 100644 --- a/go/enclave/storage/enclavedb/events.go +++ b/go/enclave/storage/enclavedb/events.go @@ -19,13 +19,16 @@ import ( ) const ( - baseEventsJoin = "from event_log e " + - "join receipt rec on e.receipt=rec.id" + - " join tx on rec.tx=tx.id " + - " left join externally_owned_account eoatx on tx.sender_address=eoatx.id " + - " join batch b on rec.batch=b.sequence " + - "join event_type et on e.event_type=et.id " + - " join contract c on et.contract=c.id " + + baseReceiptJoin = " from receipt rec " + + "join batch b on rec.batch=b.sequence " + + "join tx curr_tx on rec.tx=curr_tx.id " + + " join externally_owned_account tx_sender on curr_tx.sender_address=tx_sender.id " + + " left join contract tx_contr on curr_tx.to_address=tx_contr.id " + + baseEventJoin = " left join event_log e on e.receipt=rec.id " + + "left join event_type et on e.event_type=et.id " + + " left join contract c on et.contract=c.id " + + //" left join tx creator_tx on c.tx=creator_tx.id " + "left join event_topic t1 on e.topic1=t1.id and et.id=t1.event_type " + " left join externally_owned_account eoa1 on t1.rel_address=eoa1.id " + "left join event_topic t2 on e.topic2=t2.id and et.id=t2.event_type " + @@ -157,16 +160,17 @@ func FilterLogs( } } - return loadLogs(ctx, db, requestingAccount, query, queryParams) + _, logs, err := loadReceiptsAndEventLogs(ctx, db, requestingAccount, query, queryParams, false) + return logs, err } func DebugGetLogs(ctx context.Context, db *sql.DB, txHash common.TxHash) ([]*tracers.DebugLogs, error) { var queryParams []any // todo - should we return the config here? - query := "select eoa1.address, eoa2.address, eoa3.address, et.config_public, et.auto_public, et.event_sig, t1.topic, t2.topic, t3.topic, datablob, b.hash, b.height, tx.hash, tx.idx, log_idx, c.address, c.auto_visibility, c.transparent " + - baseEventsJoin + - " AND tx.hash = ? " + query := "select eoa1.address, eoa2.address, eoa3.address, et.config_public, et.auto_public, et.event_sig, t1.topic, t2.topic, t3.topic, datablob, b.hash, b.height, curr_tx.hash, curr_tx.idx, log_idx, c.address, c.auto_visibility, c.transparent " + + baseReceiptJoin + baseEventJoin + + " AND curr_tx.hash = ? " queryParams = append(queryParams, txHash.Bytes()) @@ -236,70 +240,250 @@ func bytesToAddress(b []byte) *gethcommon.Address { return nil } -// utility function that knows how to load relevant logs from the database -// todo always pass in the actual batch hashes because of reorgs, or make sure to clean up log entries from discarded batches -func loadLogs(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Address, whereCondition string, whereParams []any) ([]*types.Log, error) { - if requestingAccount == nil { // todo - only restrict to lifecycle events if requesting==nil - return nil, fmt.Errorf("logs can only be requested for an account") +func loadReceiptList(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Address, whereCondition string, whereParams []any, orderBy string, orderByParams []any) ([]*core.InternalReceipt, error) { + if requestingAccount == nil { + return nil, fmt.Errorf("you have to specify requestingAccount") } - - result := make([]*types.Log, 0) - 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 visibility rules - visibQuery, visibParams := visibilityQuery(requestingAccount) + query := "select b.hash, b.height, curr_tx.hash, curr_tx.idx, rec.post_state, rec.status, rec.cumulative_gas_used, rec.effective_gas_price, rec.created_contract_address, curr_tx.content, tx_sender.address, tx_contr.address, curr_tx.type " + query += baseReceiptJoin - query += visibQuery - queryParams = append(queryParams, visibParams...) + // visibility + query += " AND tx_sender.address = ? " + queryParams = append(queryParams, requestingAccount.Bytes()) query += whereCondition queryParams = append(queryParams, whereParams...) + if len(orderBy) > 0 { + query += orderBy + queryParams = append(queryParams, orderByParams...) + } + rows, err := db.QueryContext(ctx, query, queryParams...) if err != nil { return nil, err } defer rows.Close() + receipts := make([]*core.InternalReceipt, 0) + + empty := true for rows.Next() { - l := types.Log{ - Topics: make([]gethcommon.Hash, 0), + empty = false + r, err := onRowWithReceipt(rows) + if err != nil { + return nil, err } - var t0, t1, t2, t3 []byte - err = rows.Scan(&t0, &t1, &t2, &t3, &l.Data, &l.BlockHash, &l.BlockNumber, &l.TxHash, &l.TxIndex, &l.Index, &l.Address) + receipts = append(receipts, r) + } + if rows.Err() != nil { + return nil, rows.Err() + } + + if empty { + return nil, errutil.ErrNotFound + } + return receipts, nil +} + +func onRowWithReceipt(rows *sql.Rows) (*core.InternalReceipt, error) { + r := core.InternalReceipt{} + + var txIndex *uint + var blockHash, transactionHash *gethcommon.Hash + var blockNumber *uint64 + res := []any{&blockHash, &blockNumber, &transactionHash, &txIndex, &r.PostState, &r.Status, &r.CumulativeGasUsed, &r.EffectiveGasPrice, &r.CreatedContract, &r.TxContent, &r.From, &r.To, &r.TxType} + + err := rows.Scan(res...) + if err != nil { + return nil, fmt.Errorf("could not load receipt from db: %w", err) + } + + r.BlockHash = *blockHash + r.BlockNumber = big.NewInt(int64(*blockNumber)) + r.TxHash = *transactionHash + r.TransactionIndex = *txIndex + return &r, nil +} + +// utility function that knows how to load relevant logs from the database together with a receipt +// returns either receipts with logs, or only logs +// this complexity is necessary to avoid executing multiple queries. +// todo always pass in the actual batch hashes because of reorgs, or make sure to clean up log entries from discarded batches +func loadReceiptsAndEventLogs(ctx context.Context, db *sql.DB, requestingAccount *gethcommon.Address, whereCondition string, whereParams []any, withReceipts bool) ([]*core.InternalReceipt, []*types.Log, error) { + logsQuery := " et.event_sig, t1.topic, t2.topic, t3.topic, datablob, log_idx, b.hash, b.height, curr_tx.hash, curr_tx.idx, c.address " + receiptQuery := " rec.post_state, rec.status, rec.cumulative_gas_used, rec.effective_gas_price, rec.created_contract_address, curr_tx.content, tx_sender.address, tx_contr.address, curr_tx.type " + + query := "select " + logsQuery + if withReceipts { + query += "," + receiptQuery + } + query += baseReceiptJoin + query += baseEventJoin + + var queryParams []any + + if requestingAccount != nil { + // Add log visibility rules + logsVisibQuery, logsVisibParams := logsVisibilityQuery(requestingAccount) + query += logsVisibQuery + queryParams = append(queryParams, logsVisibParams...) + + // add receipt visibility rules + if withReceipts { + receiptsVisibQuery, receiptsVisibParams := receiptsVisibilityQuery(requestingAccount) + query += receiptsVisibQuery + queryParams = append(queryParams, receiptsVisibParams...) + } + } + + query += whereCondition + queryParams = append(queryParams, whereParams...) + + if withReceipts && requestingAccount != nil { + // there is a corner case when a receipt has logs, but none are visible to the requester + query += " UNION ALL " + query += " select null, null, null, null, null, null, b.hash, b.height, curr_tx.hash, curr_tx.idx, null, " + receiptQuery + query += baseReceiptJoin + query += " where b.is_canonical=true " + query += " AND tx_sender.address = ? " + queryParams = append(queryParams, requestingAccount.Bytes()) + query += whereCondition + queryParams = append(queryParams, whereParams...) + } + + rows, err := db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + receipts := make([]*core.InternalReceipt, 0) + logList := make([]*types.Log, 0) + + empty := true + for rows.Next() { + empty = false + r, l, err := onRowWithEventLogAndReceipt(rows, withReceipts) if err != nil { - return nil, fmt.Errorf("could not load log entry from db: %w", err) + return nil, nil, err + } + receipts = append(receipts, r) + logList = append(logList, l) + } + if rows.Err() != nil { + return nil, nil, rows.Err() + } + + if withReceipts { + if empty { + return nil, nil, errutil.ErrNotFound } + // group the logs manually to avoid complicating the db indexes + result := groupReceiptAndLogs(receipts, logList) + return result, nil, nil + } + return nil, logList, nil +} + +func groupReceiptAndLogs(receipts []*core.InternalReceipt, logs []*types.Log) []*core.InternalReceipt { + recMap := make(map[gethcommon.Hash]*core.InternalReceipt) + logMap := make(map[gethcommon.Hash][]*types.Log) + for _, r := range receipts { + recMap[r.TxHash] = r + } + for _, log := range logs { + logList := logMap[log.TxHash] + if logList == nil { + logList = make([]*types.Log, 0) + logMap[log.TxHash] = logList + } + logMap[log.TxHash] = append(logList, log) + } + result := make([]*core.InternalReceipt, 0) + for txHash, receipt := range recMap { + receipt.Logs = logMap[txHash] + result = append(result, receipt) + } + return result +} +func onRowWithEventLogAndReceipt(rows *sql.Rows, withReceipts bool) (*core.InternalReceipt, *types.Log, error) { + l := types.Log{ + Topics: make([]gethcommon.Hash, 0), + } + r := core.InternalReceipt{} + + var t0, t1, t2, t3 []byte + var logIndex, txIndex *uint + var blockHash, transactionHash *gethcommon.Hash + var address *gethcommon.Address + var blockNumber *uint64 + res := []any{&t0, &t1, &t2, &t3, &l.Data, &logIndex, &blockHash, &blockNumber, &transactionHash, &txIndex, &address} + + if withReceipts { + // when loading receipts, add the extra fields + res = append(res, &r.PostState, &r.Status, &r.CumulativeGasUsed, &r.EffectiveGasPrice, &r.CreatedContract, &r.TxContent, &r.From, &r.To, &r.TxType) + } + + err := rows.Scan(res...) + if err != nil { + return nil, nil, fmt.Errorf("could not load log entry from db: %w", err) + } + + if withReceipts { + r.BlockHash = *blockHash + r.BlockNumber = big.NewInt(int64(*blockNumber)) + r.TxHash = *transactionHash + r.TransactionIndex = *txIndex + } + + if logIndex != nil { + l.Index, l.BlockHash, l.BlockNumber, l.TxHash, l.TxIndex = *logIndex, *blockHash, *blockNumber, *transactionHash, *txIndex + if address != nil { + l.Address = *address + } for _, topic := range [][]byte{t0, t1, t2, t3} { if len(topic) > 0 { l.Topics = append(l.Topics, byteArrayToHash(topic)) } } - - result = append(result, &l) } - if rows.Err() != nil { - return nil, rows.Err() + if withReceipts { + return &r, &l, nil } + return nil, &l, nil +} - return result, nil +func receiptsVisibilityQuery(requestingAccount *gethcommon.Address) (string, []any) { + // the visibility rules for the receipt: + // - the sender can query + // - anyone can query if the contract is transparent + // - anyone who can view an event log should also be able to view the receipt + query := " AND ( (e.id IS NOT NULL) OR (tx_sender.address = ?) OR (tx_contr.transparent=true) )" + queryParams := []any{requestingAccount.Bytes()} + return query, queryParams } // this function encodes the event log visibility rules -func visibilityQuery(requestingAccount *gethcommon.Address) (string, []any) { +func logsVisibilityQuery(requestingAccount *gethcommon.Address) (string, []any) { acc := requestingAccount.Bytes() - visibQuery := "AND (" visibParams := make([]any, 0) + visibQuery := "AND (" + + // this condition only affects queries that return receipts that have no events logs + visibQuery += " (e.id is NULL) " + // everyone can query config_public events - visibQuery += " et.config_public=true " + visibQuery += " OR (et.config_public=true) " - // For event logs that have no explicit configuration, an event is visible be all account owners whose addresses are used in any topic - visibQuery += " OR (et.auto_visibility=true AND (et.auto_public=true OR (eoa1.address=? OR eoa2.address=? OR eoa3.address=?))) " + // For event logs that have no explicit configuration, an event is visible by all account owners whose addresses are used in any topic + visibQuery += " OR (et.auto_visibility=true AND (et.auto_public=true OR eoa1.address=? OR eoa2.address=? OR eoa3.address=?)) " visibParams = append(visibParams, acc) visibParams = append(visibParams, acc) visibParams = append(visibParams, acc) @@ -311,7 +495,7 @@ func visibilityQuery(requestingAccount *gethcommon.Address) (string, []any) { " (et.topic1_can_view AND eoa1.address=?) " + " OR (et.topic2_can_view AND eoa2.address=?) " + " OR (et.topic3_can_view AND eoa3.address=?)" + - " OR (et.sender_can_view AND eoatx.address=?)" + + " OR (et.sender_can_view AND tx_sender.address=?)" + " )" + ")" visibParams = append(visibParams, acc) @@ -352,9 +536,9 @@ func ReadEoa(ctx context.Context, dbTx *sql.Tx, addr gethcommon.Address) (uint64 return id, nil } -func WriteContractConfig(ctx context.Context, dbTX *sql.Tx, contractAddress gethcommon.Address, eoaId uint64, cfg *core.ContractVisibilityConfig) (*uint64, error) { - insert := "insert into contract (address, creator, auto_visibility, transparent) values (?,?,?,?)" - res, err := dbTX.ExecContext(ctx, insert, contractAddress.Bytes(), eoaId, cfg.AutoConfig, cfg.Transparent) +func WriteContractConfig(ctx context.Context, dbTX *sql.Tx, contractAddress gethcommon.Address, eoaId uint64, cfg *core.ContractVisibilityConfig, txId uint64) (*uint64, error) { + insert := "insert into contract (address, creator, auto_visibility, transparent, tx) values (?,?,?,?,?)" + res, err := dbTX.ExecContext(ctx, insert, contractAddress.Bytes(), eoaId, cfg.AutoConfig, cfg.Transparent, txId) if err != nil { return nil, err } diff --git a/go/enclave/storage/events_storage.go b/go/enclave/storage/events_storage.go index 6dc75ff5c8..6dcb9f2863 100644 --- a/go/enclave/storage/events_storage.go +++ b/go/enclave/storage/events_storage.go @@ -9,7 +9,6 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" gethlog "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ten-protocol/go-ten/go/common" "github.com/ten-protocol/go-ten/go/common/errutil" "github.com/ten-protocol/go-ten/go/common/measure" @@ -35,7 +34,7 @@ func (es *eventsStorage) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql // store the contracts created by this tx for createdContract, cfg := range txExecResult.CreatedContracts { - err := es.storeNewContractWithEventTypeConfigs(ctx, dbTX, createdContract, senderId, cfg) + err := es.storeNewContractWithEventTypeConfigs(ctx, dbTX, createdContract, senderId, cfg, *txId) if err != nil { return err } @@ -56,8 +55,8 @@ func (es *eventsStorage) storeReceiptAndEventLogs(ctx context.Context, dbTX *sql return nil } -func (es *eventsStorage) storeNewContractWithEventTypeConfigs(ctx context.Context, dbTX *sql.Tx, contractAddr gethcommon.Address, senderId *uint64, cfg *core.ContractVisibilityConfig) error { - _, err := enclavedb.WriteContractConfig(ctx, dbTX, contractAddr, *senderId, cfg) +func (es *eventsStorage) storeNewContractWithEventTypeConfigs(ctx context.Context, dbTX *sql.Tx, contractAddr gethcommon.Address, senderId *uint64, cfg *core.ContractVisibilityConfig, txId uint64) error { + _, err := enclavedb.WriteContractConfig(ctx, dbTX, contractAddr, *senderId, cfg, txId) if err != nil { return fmt.Errorf("could not write contract address. cause %w", err) } @@ -86,17 +85,8 @@ func (es *eventsStorage) storeNewContractWithEventTypeConfigs(ctx context.Contex return nil } -// 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 func (es *eventsStorage) storeReceipt(ctx context.Context, dbTX *sql.Tx, batch *common.BatchHeader, txExecResult *core.TxExecResult, txId *uint64) (uint64, error) { - storageReceipt := (*types.ReceiptForStorage)(txExecResult.Receipt) - receiptBytes, err := rlp.EncodeToBytes(storageReceipt) - if err != nil { - return 0, fmt.Errorf("failed to encode block receipts. Cause: %w", err) - } - - execTxId, err := enclavedb.WriteReceipt(ctx, dbTX, batch.SequencerOrderNo.Uint64(), txId, receiptBytes) + execTxId, err := enclavedb.WriteReceipt(ctx, dbTX, batch.SequencerOrderNo.Uint64(), txId, txExecResult.Receipt) if err != nil { return 0, fmt.Errorf("could not write receipt. Cause: %w", err) } diff --git a/go/enclave/storage/init/edgelessdb/001_init.sql b/go/enclave/storage/init/edgelessdb/001_init.sql index 72c12ca284..620252eb94 100644 --- a/go/enclave/storage/init/edgelessdb/001_init.sql +++ b/go/enclave/storage/init/edgelessdb/001_init.sql @@ -87,6 +87,8 @@ create table if not exists tendb.tx id INTEGER AUTO_INCREMENT, hash binary(32) NOT NULL, content mediumblob NOT NULL, + to_address int, + type int8 NOT NULL, sender_address int NOT NULL, idx int NOT NULL, batch_height int NOT NULL, @@ -98,10 +100,14 @@ create table if not exists tendb.tx create table if not exists tendb.receipt ( - id INTEGER AUTO_INCREMENT, - content mediumblob, - tx int, - batch int NOT NULL, + id INTEGER AUTO_INCREMENT, + post_state binary(32), + status int not null, + cumulative_gas_used int not null, + effective_gas_price int, + created_contract_address binary(20), + tx int, + batch int NOT NULL, INDEX (batch), INDEX (tx, batch), primary key (id) @@ -114,6 +120,7 @@ create table if not exists tendb.contract creator int NOT NULL, auto_visibility boolean NOT NULL, transparent boolean, + tx INTEGER, 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 da9ed5b10b..0717fe2513 100644 --- a/go/enclave/storage/init/sqlite/001_init.sql +++ b/go/enclave/storage/init/sqlite/001_init.sql @@ -82,6 +82,8 @@ create table if not exists tx id INTEGER PRIMARY KEY AUTOINCREMENT, hash binary(32) NOT NULL, content mediumblob NOT NULL, + to_address int, + type int8 NOT NULL, sender_address int NOT NULL REFERENCES externally_owned_account, idx int NOT NULL, batch_height int NOT NULL @@ -92,11 +94,15 @@ create index IDX_TX_BATCH_HEIGHT on tx (batch_height, idx); create table if not exists receipt ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - content mediumblob, + id INTEGER PRIMARY KEY AUTOINCREMENT, + post_state binary(32), + status int not null, + cumulative_gas_used int not null, + effective_gas_price int, + created_contract_address binary(20), -- commenting out the fk until synthetic transactions are also stored - tx INTEGER, - batch INTEGER NOT NULL REFERENCES batch + tx INTEGER, + batch INTEGER NOT NULL REFERENCES batch ); create index IDX_EX_TX_BATCH on receipt (batch); create index IDX_EX_TX_CCA on receipt (tx); @@ -108,7 +114,8 @@ create table if not exists contract -- the tx signer that created the contract creator int NOT NULL REFERENCES externally_owned_account, auto_visibility boolean NOT NULL, - transparent boolean + transparent boolean, + tx INTEGER NOT NULL REFERENCES tx ); create index IDX_CONTRACT_AD on contract (address); diff --git a/go/enclave/storage/interfaces.go b/go/enclave/storage/interfaces.go index 5bf5107be8..f4a160926a 100644 --- a/go/enclave/storage/interfaces.go +++ b/go/enclave/storage/interfaces.go @@ -99,7 +99,8 @@ type TransactionStorage interface { // GetTransaction - returns the positional metadata of the tx by hash GetTransaction(ctx context.Context, txHash common.L2TxHash) (*types.Transaction, common.L2BatchHash, uint64, uint64, error) // GetTransactionReceipt - returns the receipt of a tx by tx hash - GetTransactionReceipt(ctx context.Context, txHash common.L2TxHash) (*types.Receipt, error) + GetTransactionReceipt(ctx context.Context, txHash common.L2TxHash, requester *gethcommon.Address, syntheticTx bool) (*core.InternalReceipt, error) + ExistsTransactionReceipt(ctx context.Context, txHash common.L2TxHash) (bool, error) } type AttestationStorage interface { @@ -157,7 +158,7 @@ type Storage interface { type ScanStorage interface { GetContractCount(ctx context.Context) (*big.Int, error) - GetTransactionsPerAddress(ctx context.Context, address *gethcommon.Address, pagination *common.QueryPagination) (types.Receipts, error) + GetTransactionsPerAddress(ctx context.Context, address *gethcommon.Address, pagination *common.QueryPagination) ([]*core.InternalReceipt, error) CountTransactionsPerAddress(ctx context.Context, addr *gethcommon.Address) (uint64, error) } diff --git a/go/enclave/storage/storage.go b/go/enclave/storage/storage.go index 6d372f3f0f..650c7d015f 100644 --- a/go/enclave/storage/storage.go +++ b/go/enclave/storage/storage.go @@ -421,9 +421,17 @@ func (s *storageImpl) GetTransaction(ctx context.Context, txHash gethcommon.Hash return enclavedb.ReadTransaction(ctx, s.db.GetSQLDB(), txHash) } -func (s *storageImpl) GetTransactionReceipt(ctx context.Context, txHash gethcommon.Hash) (*types.Receipt, error) { +func (s *storageImpl) GetTransactionReceipt(ctx context.Context, txHash common.L2TxHash, requester *gethcommon.Address, syntheticTx bool) (*core.InternalReceipt, error) { defer s.logDuration("GetTransactionReceipt", measure.NewStopwatch()) - return enclavedb.ReadReceipt(ctx, s.db.GetSQLDB(), txHash, s.chainConfig) + if !syntheticTx && requester == nil { + return nil, errors.New("requester address is required for non-synthetic transactions") + } + return enclavedb.ReadReceipt(ctx, s.db.GetSQLDB(), txHash, requester) +} + +func (s *storageImpl) ExistsTransactionReceipt(ctx context.Context, txHash common.L2TxHash) (bool, error) { + defer s.logDuration("GetTransactionReceipt", measure.NewStopwatch()) + return enclavedb.ExistsReceipt(ctx, s.db.GetSQLDB(), txHash) } func (s *storageImpl) FetchAttestedKey(ctx context.Context, address gethcommon.Address) (*ecdsa.PublicKey, error) { @@ -542,12 +550,12 @@ 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, err := s.handleTxSenders(ctx, batch, dbTx) + senders, toContracts, err := s.handleTxSendersAndReceivers(ctx, batch, dbTx) if err != nil { return err } - if err := enclavedb.WriteTransactions(ctx, dbTx, batch, senders); err != nil { + if err := enclavedb.WriteTransactions(ctx, dbTx, batch, senders, toContracts); err != nil { return fmt.Errorf("could not write transactions. Cause: %w", err) } } @@ -560,21 +568,34 @@ 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)) +func (s *storageImpl) handleTxSendersAndReceivers(ctx context.Context, batch *core.Batch, dbTx *sql.Tx) ([]uint64, []*uint64, error) { + senders := make([]uint64, len(batch.Transactions)) + toContracts := 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) + sender, err := core.GetTxSigner(tx) if err != nil { - return nil, fmt.Errorf("could not read tx sender. Cause: %w", err) + return nil, nil, fmt.Errorf("could not read tx sender. Cause: %w", err) } eoaID, err := s.readOrWriteEOA(ctx, dbTx, sender) if err != nil { - return nil, fmt.Errorf("could not insert EOA. cause: %w", err) + return nil, nil, fmt.Errorf("could not insert EOA. cause: %w", err) + } + s.logger.Trace("Tx sender", "tx", tx.Hash(), "sender", sender.Hex(), "eoaId", *eoaID) + senders[i] = *eoaID + + to := tx.To() + if to != nil { + ctr, err := s.ReadContract(ctx, *to) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + return nil, nil, fmt.Errorf("could not read contract. cause: %w", err) + } + if ctr != nil { + toContracts[i] = &ctr.Id + } } - senders[i] = eoaID } - return senders, nil + return senders, toContracts, nil } func (s *storageImpl) StoreExecutedBatch(ctx context.Context, batch *common.BatchHeader, results []*core.TxExecResult) error { @@ -775,9 +796,9 @@ func (s *storageImpl) BatchWasExecuted(ctx context.Context, hash common.L2BatchH return enclavedb.BatchWasExecuted(ctx, s.db.GetSQLDB(), hash) } -func (s *storageImpl) GetTransactionsPerAddress(ctx context.Context, address *gethcommon.Address, pagination *common.QueryPagination) (types.Receipts, error) { +func (s *storageImpl) GetTransactionsPerAddress(ctx context.Context, requester *gethcommon.Address, pagination *common.QueryPagination) ([]*core.InternalReceipt, error) { defer s.logDuration("GetTransactionsPerAddress", measure.NewStopwatch()) - return enclavedb.GetTransactionsPerAddress(ctx, s.db.GetSQLDB(), s.chainConfig, address, pagination) + return enclavedb.GetTransactionsPerAddress(ctx, s.db.GetSQLDB(), requester, pagination) } func (s *storageImpl) CountTransactionsPerAddress(ctx context.Context, address *gethcommon.Address) (uint64, error) { diff --git a/go/enclave/system/contracts.go b/go/enclave/system/contracts.go index c1ba68d2c2..22cbdf5fea 100644 --- a/go/enclave/system/contracts.go +++ b/go/enclave/system/contracts.go @@ -26,7 +26,7 @@ func GenerateDeploymentTransaction(initCode []byte, wallet wallet.Wallet, logger Value: gethcommon.Big0, Gas: 500_000_000, // It's quite the expensive contract. GasPrice: gethcommon.Big0, // Synthetic transactions are on the house. Or the house. - Data: initCode, //gethcommon.FromHex(SystemDeployer.SystemDeployerMetaData.Bin), + Data: initCode, // gethcommon.FromHex(SystemDeployer.SystemDeployerMetaData.Bin), To: nil, // Geth requires nil instead of gethcommon.Address{} which equates to zero address in order to return receipt. } diff --git a/go/enclave/system/hooks.go b/go/enclave/system/hooks.go index b3624367b7..733a1434a0 100644 --- a/go/enclave/system/hooks.go +++ b/go/enclave/system/hooks.go @@ -79,13 +79,13 @@ func (s *systemContractCallbacks) Load() error { return fmt.Errorf("genesis batch does not have enough transactions") } - receipt, err := s.storage.GetTransactionReceipt(context.Background(), batch.Transactions[1].Hash()) + receipt, err := s.storage.GetTransactionReceipt(context.Background(), batch.Transactions[1].Hash(), nil, true) if err != nil { s.logger.Error("Load: Failed fetching receipt", "transactionHash", batch.Transactions[1].Hash().Hex(), "error", err) return fmt.Errorf("failed fetching receipt %w", err) } - addresses, err := DeriveAddresses(receipt) + addresses, err := DeriveAddresses(receipt.ToReceipt()) if err != nil { s.logger.Error("Load: Failed deriving addresses", "error", err, "receiptHash", receipt.TxHash.Hex()) return fmt.Errorf("failed deriving addresses %w", err) @@ -173,7 +173,7 @@ func (s *systemContractCallbacks) CreateOnBatchEndTransaction(ctx context.Contex transaction.To = gethcommon.Address{} // Zero address - contract deployment } - sender, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + sender, err := core.GetTxSigner(tx) if err != nil { s.logger.Error("CreateOnBatchEndTransaction: Failed to recover sender address", "error", err, "transactionHash", tx.Hash().Hex()) return nil, fmt.Errorf("failed to recover sender address: %w", err) diff --git a/integration/simulation/p2p/in_mem_obscuro_client.go b/integration/simulation/p2p/in_mem_obscuro_client.go index 4f7b192ee5..dd18f6edd9 100644 --- a/integration/simulation/p2p/in_mem_obscuro_client.go +++ b/integration/simulation/p2p/in_mem_obscuro_client.go @@ -126,6 +126,7 @@ func (c *inMemTenClient) Call(result interface{}, method string, args ...interfa return fmt.Errorf("RPC method %s is unknown", method) } } + func (c *inMemTenClient) getCode(result interface{}, args []interface{}) error { address, ok := args[0].(gethcommon.Address) if !ok { diff --git a/integration/tengateway/tengateway_test.go b/integration/tengateway/tengateway_test.go index a113ec270d..2768a106b6 100644 --- a/integration/tengateway/tengateway_test.go +++ b/integration/tengateway/tengateway_test.go @@ -488,8 +488,14 @@ func testErrorHandling(t *testing.T, startPort int, httpURL, wsURL string, w wal err = ogClient.RegisterAccount(w.PrivateKey(), w.Address()) require.NoError(t, err) + privateTxs, _ := json.Marshal(common.ListPrivateTransactionsQueryParams{ + Address: gethcommon.HexToAddress("0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77"), + Pagination: common.QueryPagination{Size: 10}, + }) + // make requests to geth for comparison for _, req := range []string{ + `{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["` + common.ListPrivateTransactionsCQMethod + `", "` + string(privateTxs) + `","latest"],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getLogs","params":[[]],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":[]}],"id":1}`, `{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x387","topics":["0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b"]}],"id":1}`, @@ -793,6 +799,16 @@ func testGetStorageAtForReturningUserID(t *testing.T, _ int, httpURL, wsURL stri if !strings.Contains(string(respBody3), expectedErr) { t.Errorf("eth_getStorageAt did not respond with error: %s, it was: %s", expectedErr, string(respBody3)) } + + privateTxs, _ := json.Marshal(common.ListPrivateTransactionsQueryParams{ + Address: gethcommon.HexToAddress("0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77"), + Pagination: common.QueryPagination{Size: 10}, + }) + + respBody4 := makeHTTPEthJSONReq(httpURL, tenrpc.GetStorageAt, user.tgClient.UserID(), []interface{}{common.ListPrivateTransactionsCQMethod, string(privateTxs), nil}) + if err = json.Unmarshal(respBody4, &response); err != nil { + t.Error("Unable to unmarshal response") + } } func makeRequestHTTP(url string, body []byte) []byte {