Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

show us the encrypted data #407

Merged
merged 5 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions analyzer/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ const (
VALUES ($1, $2, $3, $4)`

RuntimeTransactionInsert = `
INSERT INTO chain.runtime_transactions (runtime, round, tx_index, tx_hash, tx_eth_hash, fee, gas_limit, gas_used, size, timestamp, method, body, "to", amount, success, error_module, error_code, error_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`
INSERT INTO chain.runtime_transactions (runtime, round, tx_index, tx_hash, tx_eth_hash, fee, gas_limit, gas_used, size, timestamp, method, body, "to", amount, evm_encrypted_format, evm_encrypted_public_key, evm_encrypted_data_nonce, evm_encrypted_data_data, evm_encrypted_result_nonce, evm_encrypted_result_data, success, error_module, error_code, error_message)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃殏馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃殝馃挩

Copy link
Collaborator

@mitjat mitjat May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃槅

I can think of a few ways around the runaway train:

  1. Create a separate evm_transactions table, move the new fields (and probably also tx_eth_hash) in there. I'm a little worried about performance because we'll need probably at least tx_eth_hash and evm_encrypted_format (?) every time we're retrieving txs, even if en masse. Or we could denormalize slightly: we create an evm_encryption table, put just the new fields in it, and additionally store an is_encrypted flag in the main runtime_transactions table, because that's likely all we'll need for the non-detailed view. It's also nice because it generalizes to non-evm encryption (= Cipher).

  2. Using a postgres composite type, i.e. a struct-typed column, to store the evm encryption info. You create them like so (first format), and use them from Go via pgtype.CompositeFields like so. The downside is that we're adding a little complexity to the DB interface/structure/usage.

  3. Same as number 2, but with JSON instead of composite types. Pretty yuck and space-hungry. I prefer the train over this.

I started off favoring 2, but I'm now more in favor of the denormalized variant of 1.

Note on performance of 1 vs 2: Bulky table rows hurt performance; first in gradual ways (because of page size and disk caches), then at 8kB per row abruptly, because postgres stores rows over 8kB in a TOAST table, so every row lookup performs an implicit JOIN. This would imply 2 is faster, because we JOIN only explicitly, and only for single-tx results. However my understanding of TOAST is that only bulky columns are moved to the overflow (= TOAST) table, so with some luck, our composite type and the existing body type would be the ones to get moved, which should result in about the same performance as 1 if we only SELECT those columns for single-tx results.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too scary to do in this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)`

RuntimeEventInsert = `
INSERT INTO chain.runtime_events (runtime, round, tx_index, tx_hash, type, body, evm_log_name, evm_log_params, related_accounts)
Expand Down
63 changes: 63 additions & 0 deletions analyzer/runtime/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/errors"
sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/oasis-indexer/analyzer/evmabi"
"github.com/oasisprotocol/oasis-indexer/common"
"github.com/oasisprotocol/oasis-indexer/log"
"github.com/oasisprotocol/oasis-indexer/storage"
)
Expand Down Expand Up @@ -50,6 +53,15 @@ type EVMTokenBalanceData struct {
Balance *big.Int
}

type EVMEncryptedData struct {
Format common.CallFormat
PublicKey []byte
DataNonce []byte
DataData []byte
ResultNonce []byte
ResultData []byte
}

type EVMDeterministicError struct {
// Note: .error is the implementation of .Error, .Unwrap etc. It is not
// in the Unwrap chain. Use something like
Expand Down Expand Up @@ -290,3 +302,54 @@ func EVMDownloadTokenBalance(ctx context.Context, logger *log.Logger, source sto
return nil, fmt.Errorf("download stale token balance type %v not handled", tokenType)
}
}

// EVMMaybeUnmarshalEncryptedData breaks down a possibly encrypted data +
// result into their encryption envelope fields. If the data is not encrypted,
// it returns nil with no error.
func EVMMaybeUnmarshalEncryptedData(data []byte, result *[]byte) (*EVMEncryptedData, error) {
var encryptedData EVMEncryptedData
var call sdkTypes.Call
if cbor.Unmarshal(data, &call) != nil {
// Invalid CBOR means it's bare Ethereum format data. This is normal.
// https://github.com/oasisprotocol/oasis-sdk/blob/runtime-sdk/v0.3.0/runtime-sdk/modules/evm/src/lib.rs#L626
return nil, nil
}
encryptedData.Format = common.CallFormat(call.Format.String())
switch call.Format {
case sdkTypes.CallFormatEncryptedX25519DeoxysII:
var callEnvelope sdkTypes.CallEnvelopeX25519DeoxysII
if err := cbor.Unmarshal(call.Body, &callEnvelope); err != nil {
return nil, fmt.Errorf("outer call format %s unmarshal body: %w", call.Format, err)
}
encryptedData.PublicKey = callEnvelope.Pk[:]
encryptedData.DataNonce = callEnvelope.Nonce[:]
encryptedData.DataData = callEnvelope.Data
// If you are adding new call formats, remember to add them to the
// database call_format enum too.
default:
return nil, fmt.Errorf("outer call format %s (%d) not supported", call.Format, call.Format)
}
var callResult sdkTypes.CallResult
if result != nil {
if err := cbor.Unmarshal(*result, &callResult); err != nil {
return nil, fmt.Errorf("unmarshal outer call result: %w", err)
}
if callResult.IsUnknown() {
switch call.Format {
case sdkTypes.CallFormatEncryptedX25519DeoxysII:
var resultEnvelope sdkTypes.ResultEnvelopeX25519DeoxysII
if err := cbor.Unmarshal(callResult.Unknown, &resultEnvelope); err != nil {
return nil, fmt.Errorf("outer call result unmarshal unknown: %w", err)
}
encryptedData.ResultNonce = resultEnvelope.Nonce[:]
encryptedData.ResultData = resultEnvelope.Data
default:
// We have already checked when decoding the call envelope,
// but I'm keeping this default case here so we don't forget
// if this code gets restructured.
return nil, fmt.Errorf("outer call format %s (%d) not supported", call.Format, call.Format)
}
}
}
return &encryptedData, nil
}
25 changes: 25 additions & 0 deletions analyzer/runtime/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type BlockTransactionData struct {
Body interface{}
To *apiTypes.Address // Extracted from the body for convenience. Semantics vary by tx type.
Amount *common.BigInt // Extracted from the body for convenience. Semantics vary by tx type.
EVMEncrypted *EVMEncryptedData
Success *bool
Error *TxError
}
Expand Down Expand Up @@ -375,6 +376,18 @@ func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []nodeapi.Runtime
return fmt.Errorf("created contract: %w", err)
}
}
var evmEncrypted *EVMEncryptedData
evmEncrypted, err = EVMMaybeUnmarshalEncryptedData(body.InitCode, ok)
if err == nil {
pro-wh marked this conversation as resolved.
Show resolved Hide resolved
blockTransactionData.EVMEncrypted = evmEncrypted
} else {
logger.Error("error unmarshalling encrypted init code and result, omitting encrypted fields",
"round", blockHeader.Round,
"tx_index", txIndex,
"tx_hash", txr.Tx.Hash(),
"err", err,
)
}
return nil
},
EVMCall: func(body *evm.Call, ok *[]byte) error {
Expand All @@ -383,6 +396,18 @@ func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []nodeapi.Runtime
if to, err = registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, body.Address); err != nil {
return fmt.Errorf("address: %w", err)
}
var evmEncrypted *EVMEncryptedData
evmEncrypted, err = EVMMaybeUnmarshalEncryptedData(body.Data, ok)
if err == nil {
blockTransactionData.EVMEncrypted = evmEncrypted
} else {
logger.Error("error unmarshalling encrypted data and result, omitting encrypted fields",
"round", blockHeader.Round,
"tx_index", txIndex,
"tx_hash", txr.Tx.Hash(),
"err", err,
)
}
// todo: maybe parse known token methods
return nil
},
Expand Down
22 changes: 22 additions & 0 deletions analyzer/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ func (m *Main) queueDbUpdates(batch *storage.QueryBatch, data *BlockData) {
for addr := range transactionData.RelatedAccountAddresses {
batch.Queue(queries.RuntimeRelatedTransactionInsert, m.cfg.RuntimeName, addr, data.Header.Round, transactionData.Index)
}
var (
evmEncryptedFormat *common.CallFormat
evmEncryptedPublicKey *[]byte
evmEncryptedDataNonce *[]byte
evmEncryptedDataData *[]byte
evmEncryptedResultNonce *[]byte
evmEncryptedResultData *[]byte
)
if transactionData.EVMEncrypted != nil {
evmEncryptedFormat = &transactionData.EVMEncrypted.Format
evmEncryptedPublicKey = &transactionData.EVMEncrypted.PublicKey
evmEncryptedDataNonce = &transactionData.EVMEncrypted.DataNonce
evmEncryptedDataData = &transactionData.EVMEncrypted.DataData
evmEncryptedResultNonce = &transactionData.EVMEncrypted.ResultNonce
evmEncryptedResultData = &transactionData.EVMEncrypted.ResultData
}
var error_module string
var error_code uint32
var error_message *string
Expand All @@ -300,6 +316,12 @@ func (m *Main) queueDbUpdates(batch *storage.QueryBatch, data *BlockData) {
transactionData.Body,
transactionData.To,
transactionData.Amount,
evmEncryptedFormat,
evmEncryptedPublicKey,
evmEncryptedDataNonce,
evmEncryptedDataData,
evmEncryptedResultNonce,
evmEncryptedResultData,
transactionData.Success,
error_module,
error_code,
Expand Down
2 changes: 2 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,5 @@ const (
RuntimeSapphire Runtime = "sapphire"
RuntimeUnknown Runtime = "unknown"
)

type CallFormat string
11 changes: 10 additions & 1 deletion storage/migrations/02_runtimes.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
BEGIN;

CREATE TYPE runtime AS ENUM ('emerald', 'sapphire', 'cipher');
CREATE TYPE call_format AS ENUM ('encrypted/x25519-deoxysii');
pro-wh marked this conversation as resolved.
Show resolved Hide resolved

CREATE TABLE chain.runtime_blocks
(
Expand Down Expand Up @@ -48,11 +49,19 @@ CREATE TABLE chain.runtime_transactions
size UINT31 NOT NULL,

-- Transaction contents.
method TEXT, -- accounts.Transter, consensus.Deposit, consensus.Withdraw, evm.Create, evm.Call. NULL for malformed and encrypted txs.
method TEXT, -- accounts.Transfer, consensus.Deposit, consensus.Withdraw, evm.Create, evm.Call. NULL for malformed and encrypted txs.
body JSONB, -- For EVM txs, the EVM method and args are encoded in here. NULL for malformed and encrypted txs.
"to" oasis_addr, -- Exact semantics depend on method. Extracted from body; for convenience only.
amount UINT_NUMERIC, -- Exact semantics depend on method. Extracted from body; for convenience only.

-- Encrypted data in encrypted Ethereum-format transactions.
evm_encrypted_format call_format,
evm_encrypted_public_key BYTEA,
evm_encrypted_data_nonce BYTEA,
evm_encrypted_data_data BYTEA,
evm_encrypted_result_nonce BYTEA,
evm_encrypted_result_data BYTEA,

-- Error information.
success BOOLEAN, -- NULL means success is unknown (can happen in confidential runtimes)
error_module TEXT,
Expand Down