diff --git a/analyzer/emerald/emerald.go b/analyzer/emerald/emerald.go index 6c409e468..57447914d 100644 --- a/analyzer/emerald/emerald.go +++ b/analyzer/emerald/emerald.go @@ -8,7 +8,9 @@ import ( "github.com/iancoleman/strcase" "github.com/jackc/pgx/v4" + ocCommon "github.com/oasisprotocol/oasis-core/go/common" oasisConfig "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature" "golang.org/x/sync/errgroup" "github.com/oasisprotocol/oasis-indexer/analyzer" @@ -269,7 +271,17 @@ func (m *Main) queueBlockInserts(batch *storage.QueryBatch, data *storage.Runtim } func (m *Main) queueTransactionInserts(batch *storage.QueryBatch, data *storage.RuntimeBlockData) error { - // TODO(ennsharma): Process Emerald transaction data in accordance with - // https://github.com/oasisprotocol/emerald-web3-gateway/blob/main/indexer/utils.go#L225 + var rtid ocCommon.Namespace + if err := rtid.UnmarshalHex("000000000000000000000000000000000000000000000000e2eaa99fc008f87f"); err != nil { + return err + } + sigContext := signature.DeriveChainContext(rtid, "b11b369e0da5bb230b220127f5e7b242d385ef8c6f54906243f30af63c815535") + chainAlias := "mainnet_emerald" + + blockData, err := extractRound(sigContext, data.BlockHeader, data.TransactionsWithResults) + if err != nil { + return fmt.Errorf("extract round %d: %w", data.Round, err) + } + emitRoundBatch(batch, chainAlias, int64(data.Round), blockData) return nil } diff --git a/analyzer/emerald/incoming.go b/analyzer/emerald/incoming.go new file mode 100644 index 000000000..cb9074efa --- /dev/null +++ b/analyzer/emerald/incoming.go @@ -0,0 +1,366 @@ +package emerald + +import ( + "bytes" + "encoding/hex" + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/common/crypto/address" + "github.com/oasisprotocol/oasis-core/go/roothash/api/block" + sdkClient "github.com/oasisprotocol/oasis-sdk/client-sdk/go/client" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/accounts" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/core" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" + + common "github.com/oasisprotocol/oasis-indexer/analyzer/uncategorized" + "github.com/oasisprotocol/oasis-indexer/storage" +) + +// todo: erc721, erc1155 + +type BlockTransactionSignerData struct { + Index int + Address string + Nonce int +} + +type BlockTransactionData struct { + Index int + Hash string + EthHash *string + SignerData []*BlockTransactionSignerData + RelatedAccountAddresses map[string]bool +} + +type AddressPreimageData struct { + ContextIdentifier string + ContextVersion int + Data []byte +} + +type BlockData struct { + Hash string + NumTransactions int + GasUsed int64 + Size int + TransactionData []*BlockTransactionData + AddressPreimages map[string]*AddressPreimageData +} + +// Function naming conventions in this file: +// 'extract-' -> dataflow from parameters to return values, no side effects. suitable for processing pieces of data +// that doesn't affect their siblings +// 'register-' -> dataflow from input parameters to output parameters, side effects. may have dataflow of something +// useful to return values as well, to entice developers to use these functions instead of e.g. converting an address +// manually and inadvertently leaving it out of a related address or address preimage map +// 'visit-' -> dataflow from generic parameter to specific callback, no side effects, although callbacks will have side +// effects. suitable for processing smaller pieces of data that contribute to aggregated structures + +func extractAddressPreimage(as *sdkTypes.AddressSpec) (*AddressPreimageData, error) { + // Adapted from oasis-sdk/client-sdk/go/types/transaction.go. + var ( + ctx address.Context + data []byte + ) + switch { + case as.Signature != nil: + spec := as.Signature + switch { + case spec.Ed25519 != nil: + ctx = sdkTypes.AddressV0Ed25519Context + data, _ = spec.Ed25519.MarshalBinary() + case spec.Secp256k1Eth != nil: + ctx = sdkTypes.AddressV0Secp256k1EthContext + // Use a scheme such that we can compute Secp256k1 addresses from Ethereum + // addresses as this makes things more interoperable. + untaggedPk, _ := spec.Secp256k1Eth.MarshalBinaryUncompressedUntagged() + data = common.SliceEthAddress(common.Keccak256(untaggedPk)) + case spec.Sr25519 != nil: + ctx = sdkTypes.AddressV0Sr25519Context + data, _ = spec.Sr25519.MarshalBinary() + default: + panic("address: unsupported public key type") + } + case as.Multisig != nil: + config := as.Multisig + ctx = sdkTypes.AddressV0MultisigContext + data = cbor.Marshal(config) + default: + return nil, fmt.Errorf("malformed AddressSpec") + } + return &AddressPreimageData{ + ContextIdentifier: ctx.Identifier, + ContextVersion: int(ctx.Version), + Data: data, + }, nil +} + +func registerAddressSpec(addressPreimages map[string]*AddressPreimageData, as *sdkTypes.AddressSpec) (string, error) { + addr, err := common.StringifyAddressSpec(as) + if err != nil { + return "", err + } + + if _, ok := addressPreimages[addr]; !ok { + preimageData, err1 := extractAddressPreimage(as) + if err1 != nil { + return "", fmt.Errorf("extract address preimage: %w", err1) + } + addressPreimages[addr] = preimageData + } + + return addr, nil +} + +func registerEthAddress(addressPreimages map[string]*AddressPreimageData, ethAddr []byte) (string, error) { + addr, err := common.StringifyEthAddress(ethAddr) + if err != nil { + return "", err + } + + if _, ok := addressPreimages[addr]; !ok { + addressPreimages[addr] = &AddressPreimageData{ + ContextIdentifier: sdkTypes.AddressV0Secp256k1EthContext.Identifier, + ContextVersion: int(sdkTypes.AddressV0Secp256k1EthContext.Version), + Data: ethAddr, + } + } + + return addr, nil +} + +//nolint:unparam +func registerRelatedSdkAddress(relatedAddresses map[string]bool, sdkAddr *sdkTypes.Address) (string, error) { + addr, err := common.StringifySdkAddress(sdkAddr) + if err != nil { + return "", err + } + + relatedAddresses[addr] = true + + return addr, nil +} + +func registerRelatedAddressSpec(addressPreimages map[string]*AddressPreimageData, relatedAddresses map[string]bool, as *sdkTypes.AddressSpec) (string, error) { + addr, err := registerAddressSpec(addressPreimages, as) + if err != nil { + return "", err + } + + relatedAddresses[addr] = true + + return addr, nil +} + +//nolint:unparam +func registerRelatedEthAddress(addressPreimages map[string]*AddressPreimageData, relatedAddresses map[string]bool, ethAddr []byte) (string, error) { + addr, err := registerEthAddress(addressPreimages, ethAddr) + if err != nil { + return "", err + } + + relatedAddresses[addr] = true + + return addr, nil +} + +//nolint:gocyclo +func extractRound(sigContext signature.Context, b *block.Block, txrs []*sdkClient.TransactionWithResults) (*BlockData, error) { + var blockData BlockData + blockData.Hash = b.Header.EncodedHash().String() + blockData.NumTransactions = len(txrs) + blockData.TransactionData = make([]*BlockTransactionData, 0, len(txrs)) + blockData.AddressPreimages = map[string]*AddressPreimageData{} + for i, txr := range txrs { + // fmt.Printf("%#v\n", txr) + var blockTransactionData BlockTransactionData + blockTransactionData.Index = i + blockTransactionData.Hash = txr.Tx.Hash().Hex() + if len(txr.Tx.AuthProofs) == 1 && txr.Tx.AuthProofs[0].Module == "evm.ethereum.v0" { + ethHash := hex.EncodeToString(common.Keccak256(txr.Tx.Body)) + blockTransactionData.EthHash = ðHash + } + blockTransactionData.RelatedAccountAddresses = map[string]bool{} + tx, err := common.VerifyUtx(sigContext, &txr.Tx) + if err != nil { + err = fmt.Errorf("tx %d: %w", i, err) + fmt.Println(err) + tx = nil + } + if tx != nil { + blockTransactionData.SignerData = make([]*BlockTransactionSignerData, 0, len(tx.AuthInfo.SignerInfo)) + for j, si := range tx.AuthInfo.SignerInfo { + var blockTransactionSignerData BlockTransactionSignerData + blockTransactionSignerData.Index = j + addr, err1 := registerRelatedAddressSpec(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, &si.AddressSpec) + if err1 != nil { + return nil, fmt.Errorf("tx %d signer %d visit address spec: %w", i, j, err1) + } + blockTransactionSignerData.Address = addr + blockTransactionSignerData.Nonce = int(si.Nonce) + blockTransactionData.SignerData = append(blockTransactionData.SignerData, &blockTransactionSignerData) + } + if err = common.VisitCall(&tx.Call, &txr.Result, &common.CallHandler{ + AccountsTransfer: func(body *accounts.Transfer) error { + if _, err = registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &body.To); err != nil { + return fmt.Errorf("to: %w", err) + } + return nil + }, + ConsensusAccountsDeposit: func(body *consensusaccounts.Deposit) error { + if _, err = registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, body.To); err != nil { + return fmt.Errorf("to: %w", err) + } + return nil + }, + ConsensusAccountsWithdraw: func(body *consensusaccounts.Withdraw) error { + // .To is from another chain, so exclude? + return nil + }, + EvmCreate: func(body *evm.Create, ok *[]byte) error { + if !txr.Result.IsUnknown() && txr.Result.IsSuccess() && len(*ok) == 32 { + // todo: is this rigorous enough? + if _, err = registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, common.SliceEthAddress(*ok)); err != nil { + return fmt.Errorf("created contract: %w", err) + } + } + return nil + }, + EvmCall: func(body *evm.Call, ok *[]byte) error { + if _, err = registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, body.Address); err != nil { + return fmt.Errorf("address: %w", err) + } + // todo: maybe parse known token methods + return nil + }, + }); err != nil { + return nil, err + } + } + var txGasUsed int64 + foundGasUsedEvent := false + if err = common.VisitSdkEvents(txr.Events, &common.SdkEventHandler{ + Core: func(event *core.Event) error { + if event.GasUsed != nil { + if foundGasUsedEvent { + return fmt.Errorf("multiple gas used events") + } + foundGasUsedEvent = true + txGasUsed = int64(event.GasUsed.Amount) + } + return nil + }, + Accounts: func(event *accounts.Event) error { + if event.Transfer != nil { + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Transfer.From); err1 != nil { + return fmt.Errorf("from: %w", err1) + } + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Transfer.To); err1 != nil { + return fmt.Errorf("to: %w", err1) + } + } + if event.Burn != nil { + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Burn.Owner); err1 != nil { + return fmt.Errorf("owner: %w", err1) + } + } + if event.Mint != nil { + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Mint.Owner); err1 != nil { + return fmt.Errorf("owner: %w", err1) + } + } + return nil + }, + ConsensusAccounts: func(event *consensusaccounts.Event) error { + if event.Deposit != nil { + // .From is from another chain, so exclude? + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Deposit.To); err1 != nil { + return fmt.Errorf("from: %w", err1) + } + } + if event.Withdraw != nil { + if _, err1 := registerRelatedSdkAddress(blockTransactionData.RelatedAccountAddresses, &event.Withdraw.From); err1 != nil { + return fmt.Errorf("from: %w", err1) + } + // .To is from another chain, so exclude? + } + return nil + }, + Evm: func(event *evm.Event) error { + // dumpEvmEvent(event) // %%% + if err1 := common.VisitEvmEvent(event, &common.EvmEventHandler{ + Erc20Transfer: func(fromEthAddr []byte, toEthAddr []byte, amountU256 []byte) error { + if !bytes.Equal(fromEthAddr, common.ZeroEthAddr) { + _, err1 := registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, fromEthAddr) + if err1 != nil { + return fmt.Errorf("from: %w", err1) + } + } + if !bytes.Equal(toEthAddr, common.ZeroEthAddr) { + _, err1 := registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, toEthAddr) + if err1 != nil { + return fmt.Errorf("to: %w", err1) + } + } + return nil + }, + Erc20Approval: func(ownerEthAddr []byte, spenderEthAddr []byte, amountU256 []byte) error { + if !bytes.Equal(ownerEthAddr, common.ZeroEthAddr) { + _, err1 := registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, ownerEthAddr) + if err1 != nil { + return fmt.Errorf("owner: %w", err1) + } + } + if !bytes.Equal(spenderEthAddr, common.ZeroEthAddr) { + _, err1 := registerRelatedEthAddress(blockData.AddressPreimages, blockTransactionData.RelatedAccountAddresses, spenderEthAddr) + if err1 != nil { + return fmt.Errorf("spender: %w", err1) + } + } + return nil + }, + }); err1 != nil { + return err1 + } + return nil + }, + }); err != nil { + return nil, fmt.Errorf("tx %d: %w", i, err) + } + if !foundGasUsedEvent { + if (txr.Result.IsUnknown() || txr.Result.IsSuccess()) && tx != nil { + // Treat as if it used all the gas. + txGasUsed = int64(tx.AuthInfo.Fee.Gas) + } else { //nolint:staticcheck + // Inaccurate: Treat as not using any gas. + } + } + blockData.TransactionData = append(blockData.TransactionData, &blockTransactionData) + blockData.GasUsed += txGasUsed + // Inaccurate: Re-serialize signed tx to estimate original size. + txSize := len(cbor.Marshal(txr.Tx)) + blockData.Size += txSize + } + return &blockData, nil +} + +func emitRoundBatch(batch *storage.QueryBatch, chainAlias string, round int64, blockData *BlockData) { + for _, transactionData := range blockData.TransactionData { + for _, signerData := range transactionData.SignerData { + batch.Queue("INSERT INTO transaction_signer (chain_alias, height, tx_index, signer_index, addr, nonce) VALUES ($1, $2, $3, $4, $5, $6)", chainAlias, round, transactionData.Index, signerData.Index, signerData.Address, signerData.Nonce) + } + for addr := range transactionData.RelatedAccountAddresses { + batch.Queue("INSERT INTO related_transaction (chain_alias, account_address, tx_height, tx_index) VALUES ($1, $2, $3, $4)", chainAlias, addr, round, transactionData.Index) + } + batch.Queue("INSERT INTO transaction_extra (chain_alias, height, tx_index, tx_hash, eth_hash) VALUES ($1, $2, $3, $4, $5)", chainAlias, round, transactionData.Index, transactionData.Hash, transactionData.EthHash) + } + for addr, preimageData := range blockData.AddressPreimages { + batch.Queue("INSERT INTO address_preimage (address, context_identifier, context_version, addr_data) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING", addr, preimageData.ContextIdentifier, preimageData.ContextVersion, preimageData.Data) + } + batch.Queue("INSERT INTO block_extra (chain_alias, height, b_hash, num_transactions, gas_used, size) VALUES ($1, $2, $3, $4, $5, $6)", chainAlias, round, blockData.Hash, blockData.NumTransactions, blockData.GasUsed, blockData.Size) + batch.Queue("UPDATE progress SET first_unscanned_height = $1 WHERE chain_alias = $2", round+1, chainAlias) +} diff --git a/analyzer/uncategorized/addresses.go b/analyzer/uncategorized/addresses.go new file mode 100644 index 000000000..2a82d4bf3 --- /dev/null +++ b/analyzer/uncategorized/addresses.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common/crypto/address" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +// nearly hungarian notation notes: +// ethAddr -> []byte len-20 slice +// ocAddr -> oasis-core type binary oasis address +// sdkAddr -> oasis-sdk type binary oasis address +// addr -> bech32 string oasis address +// addrTextBytes -> bech32 []byte oasis address + +func StringifySdkAddress(sdkAddr *sdkTypes.Address) (string, error) { + addrTextBytes, err := sdkAddr.MarshalText() + if err != nil { + return "", fmt.Errorf("address marshal text: %w", err) + } + return string(addrTextBytes), nil +} + +func StringifyAddressSpec(as *sdkTypes.AddressSpec) (string, error) { + sdkAddr, err := as.Address() + if err != nil { + return "", fmt.Errorf("derive address: %w", err) + } + return StringifySdkAddress(&sdkAddr) +} + +func StringifyOcAddress(ocAddr address.Address) (string, error) { + sdkAddr := (sdkTypes.Address)(ocAddr) + return StringifySdkAddress(&sdkAddr) +} + +func StringifyEthAddress(ethAddr []byte) (string, error) { + ctx := sdkTypes.AddressV0Secp256k1EthContext + ocAddr := address.NewAddress(ctx, ethAddr) + return StringifyOcAddress(ocAddr) +} diff --git a/analyzer/uncategorized/eth.go b/analyzer/uncategorized/eth.go new file mode 100644 index 000000000..9f1065ef2 --- /dev/null +++ b/analyzer/uncategorized/eth.go @@ -0,0 +1,22 @@ +package common + +import ( + "golang.org/x/crypto/sha3" +) + +var ( + TopicErc20Transfer = Keccak256([]byte("Transfer(address,address,uint256)")) + TopicErc20Approval = Keccak256([]byte("Approval(address,address,uint256)")) +) + +var ZeroEthAddr = make([]byte, 20) + +func Keccak256(data []byte) []byte { + h := sha3.NewLegacyKeccak256() + h.Write(data) + return h.Sum(nil) +} + +func SliceEthAddress(b32 []byte) []byte { + return b32[32-20:] +} diff --git a/analyzer/uncategorized/evm_raw_tx.go b/analyzer/uncategorized/evm_raw_tx.go new file mode 100644 index 000000000..541c681b9 --- /dev/null +++ b/analyzer/uncategorized/evm_raw_tx.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/oasisprotocol/oasis-core/go/common/quantity" + sdkClient "github.com/oasisprotocol/oasis-sdk/client-sdk/go/client" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/secp256k1" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +func decodeEthRawTx(body []byte, expectedChainID uint64) (*sdkTypes.Transaction, error) { + var ethTx ethTypes.Transaction + if err := ethTx.UnmarshalBinary(body); err != nil { + return nil, fmt.Errorf("rlp decode bytes: %w", err) + } + evmV1 := evm.NewV1(nil) + var tb *sdkClient.TransactionBuilder + if to := ethTx.To(); to != nil { + tb = evmV1.Call(to.Bytes(), ethTx.Value().Bytes(), ethTx.Data()) + } else { + tb = evmV1.Create(ethTx.Value().Bytes(), ethTx.Data()) + } + chainIDBI := ethTx.ChainId() + if !chainIDBI.IsUint64() || chainIDBI.Uint64() != expectedChainID { + return nil, fmt.Errorf("chain ID %v, expected %v", chainIDBI, expectedChainID) + } + signer := ethTypes.LatestSignerForChainID(chainIDBI) + pubUncompressed, err := LondonSenderPub(signer, ðTx) + if err != nil { + return nil, fmt.Errorf("recover signer public key: %w", err) + } + var sender secp256k1.PublicKey + if err = sender.UnmarshalBinary(pubUncompressed); err != nil { + return nil, fmt.Errorf("sdk secp256k1 public key unmarshal binary: %w", err) + } + // Base fee is zero. Allocate only priority fee. + gasPrice := ethTx.GasTipCap() + if ethTx.GasFeeCapIntCmp(gasPrice) < 0 { + gasPrice = ethTx.GasFeeCap() + } + var resolvedFeeAmount quantity.Quantity + if err = resolvedFeeAmount.FromBigInt(gasPrice); err != nil { + return nil, fmt.Errorf("converting gas price: %w", err) + } + if err = resolvedFeeAmount.Mul(quantity.NewFromUint64(ethTx.Gas())); err != nil { + return nil, fmt.Errorf("computing total fee amount: %w", err) + } + tb.AppendAuthSignature(sdkTypes.SignatureAddressSpec{Secp256k1Eth: &sender}, ethTx.Nonce()) + tb.SetFeeAmount(sdkTypes.NewBaseUnits(resolvedFeeAmount, sdkTypes.NativeDenomination)) + tb.SetFeeGas(ethTx.Gas()) + return tb.GetTransaction(), nil +} diff --git a/analyzer/uncategorized/evm_raw_tx_test.go b/analyzer/uncategorized/evm_raw_tx_test.go new file mode 100644 index 000000000..33c8e6805 --- /dev/null +++ b/analyzer/uncategorized/evm_raw_tx_test.go @@ -0,0 +1,229 @@ +package common + +import ( + "encoding/hex" + "math/big" + "strings" + "testing" + + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-core/go/common/quantity" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" + "github.com/stretchr/testify/require" +) + +// These tests are based on oasis-sdk/runtime-sdk/modules/evm/src/raw_tx.rs. + +func decodeExpectCall( + t *testing.T, + raw string, + expectedChainID uint64, + expectedTo string, + expectedValue uint64, + expectedData string, //nolint:unparam + expectedGasLimit uint64, + expectedGasPrice uint64, + expectedFrom string, + expectedNonce uint64, +) { + rawBytes, err := hex.DecodeString(raw) + require.NoError(t, err) + tx, err := decodeEthRawTx(rawBytes, expectedChainID) + require.NoError(t, err) + t.Logf("%#v\n", tx) + require.Equal(t, tx.Call.Method, "evm.Call") + var body evm.Call + require.NoError(t, cbor.Unmarshal(tx.Call.Body, &body)) + expectedToBytes, err := hex.DecodeString(expectedTo) + require.NoError(t, err) + require.Equal(t, expectedToBytes, body.Address) + var bodyValueBI big.Int + bodyValueBI.SetBytes(body.Value) + require.True(t, bodyValueBI.IsUint64()) + require.Equal(t, expectedValue, bodyValueBI.Uint64()) + expectedDataBytes, err := hex.DecodeString(expectedData) + require.NoError(t, err) + require.Equal(t, expectedDataBytes, body.Data) + require.Len(t, tx.AuthInfo.SignerInfo, 1) + from0xChecksummed := helpers.EthAddressFromPubKey(*tx.AuthInfo.SignerInfo[0].AddressSpec.Signature.Secp256k1Eth) + fromChecksummed := from0xChecksummed[2:] + from := strings.ToLower(fromChecksummed) + require.Equal(t, expectedFrom, from) + require.Equal(t, expectedNonce, tx.AuthInfo.SignerInfo[0].Nonce) + feeAmount := quantity.NewFromUint64(expectedGasLimit) + err = feeAmount.Mul(quantity.NewFromUint64(expectedGasPrice)) + require.NoError(t, err) + require.Equal(t, feeAmount, &tx.AuthInfo.Fee.Amount.Amount) + require.Equal(t, sdkTypes.NativeDenomination, tx.AuthInfo.Fee.Amount.Denomination) + require.Equal(t, expectedGasLimit, tx.AuthInfo.Fee.Gas) +} + +func decodeExpectCreate( + t *testing.T, + raw string, + expectedChainID uint64, + expectedValue uint64, + expectedInitCode string, + expectedGasLimit uint64, + expectedGasPrice uint64, + expectedFrom string, + expectedNonce uint64, +) { + rawBytes, err := hex.DecodeString(raw) + require.NoError(t, err) + tx, err := decodeEthRawTx(rawBytes, expectedChainID) + require.NoError(t, err) + t.Logf("%#v\n", tx) + require.Equal(t, tx.Call.Method, "evm.Create") + var body evm.Create + require.NoError(t, cbor.Unmarshal(tx.Call.Body, &body)) + var bodyValueBI big.Int + bodyValueBI.SetBytes(body.Value) + require.True(t, bodyValueBI.IsUint64()) + require.Equal(t, expectedValue, bodyValueBI.Uint64()) + expectedInitCodeBytes, err := hex.DecodeString(expectedInitCode) + require.NoError(t, err) + require.Equal(t, expectedInitCodeBytes, body.InitCode) + require.Len(t, tx.AuthInfo.SignerInfo, 1) + from0xChecksummed := helpers.EthAddressFromPubKey(*tx.AuthInfo.SignerInfo[0].AddressSpec.Signature.Secp256k1Eth) + fromChecksummed := from0xChecksummed[2:] + from := strings.ToLower(fromChecksummed) + require.Equal(t, expectedFrom, from) + require.Equal(t, expectedNonce, tx.AuthInfo.SignerInfo[0].Nonce) + feeAmount := quantity.NewFromUint64(expectedGasLimit) + err = feeAmount.Mul(quantity.NewFromUint64(expectedGasPrice)) + require.NoError(t, err) + require.Equal(t, feeAmount, &tx.AuthInfo.Fee.Amount.Amount) + require.Equal(t, sdkTypes.NativeDenomination, tx.AuthInfo.Fee.Amount.Denomination) + require.Equal(t, expectedGasLimit, tx.AuthInfo.Fee.Gas) +} + +func decodeExpectInvalid(t *testing.T, raw string, expectedChainID uint64) { + rawBytes, err := hex.DecodeString(raw) + require.NoError(t, err) + _, err = decodeEthRawTx(rawBytes, expectedChainID) + require.Error(t, err) + t.Logf("%#v\n", err) +} + +func decodeExpectFromMismatch( + t *testing.T, + raw string, + expectedChainID uint64, + unexpectedFrom string, +) { + rawBytes, err := hex.DecodeString(raw) + require.NoError(t, err) + tx, err := decodeEthRawTx(rawBytes, expectedChainID) + require.NoError(t, err) + t.Logf("%#v\n", tx) + require.Len(t, tx.AuthInfo.SignerInfo, 1) + from0xChecksummed := helpers.EthAddressFromPubKey(*tx.AuthInfo.SignerInfo[0].AddressSpec.Signature.Secp256k1Eth) + fromChecksummed := from0xChecksummed[2:] + from := strings.ToLower(fromChecksummed) + require.NotEqual(t, unexpectedFrom, from) +} + +func TestDecodeBasic(t *testing.T) { + decodeExpectCall( + t, + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", + 0, + "13978aee95f38490e9769c39b2773ed763d9cd5f", + 10_000_000_000_000_000, + "", + 10_000, + 1_000_000_000_000, + // "cow" test account + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + 0, + ) + decodeExpectCreate( + t, + // We're using a transaction normalized from the original (below) to have low `s`. + // f87f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f21ba05afed0244d0da90b67cf8979b0f246432a5112c0d31e8d5eedd2bc17b171c694a0bb1035c834677c2e1185b8dc90ca6d1fa585ab3d7ef23707e1a497a98e752d1b + "f87f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f21ca05afed0244d0da90b67cf8979b0f246432a5112c0d31e8d5eedd2bc17b171c694a044efca37cb9883d1ee7a47236f3592df152931a930566933de2dc6e341c11426", + 0, + 0, + "6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f2", + 10_000, + 1_000_000_000_000, + // "horse" test account + "13978aee95f38490e9769c39b2773ed763d9cd5f", + 0, + ) +} + +func TestDecodeChainId(t *testing.T) { + // Test with mismatching expect_chain_id to exercise our check. + decodeExpectInvalid( + t, + // Taken from test_decode_basic. + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", + 5, + ) +} + +func TestDecodeTypes(t *testing.T) { + // https://github.com/ethereum/tests/blob/v10.0/BlockchainTests/ValidBlocks/bcEIP1559/transType.json + decodeExpectCall( + t, + "f861018203e882c35094cccccccccccccccccccccccccccccccccccccccc80801ca021539ef96c70ab75350c594afb494458e211c8c722a7a0ffb7025c03b87ad584a01d5395fe48edb306f614f0cd682b8c2537537f5fd3e3275243c42e9deff8e93d", + 0, + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 1_000, + "d02d72e067e77158444ef2020ff2d325f929b363", + 1, + ) + decodeExpectCall( + t, + "01f86301028203e882c35094cccccccccccccccccccccccccccccccccccccccc8080c080a0260f95e555a1282ef49912ff849b2007f023c44529dc8fb7ecca7693cccb64caa06252cf8af2a49f4cb76fd7172feaece05124edec02db242886b36963a30c2606", + 1, + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 1_000, + "d02d72e067e77158444ef2020ff2d325f929b363", + 2, + ) + decodeExpectCall( + t, + "02f8640103648203e882c35094cccccccccccccccccccccccccccccccccccccccc8080c001a08480e6848952a15ae06192b8051d213d689bdccdf8f14cf69f61725e44e5e80aa057c2af627175a2ac812dab661146dfc7b9886e885c257ad9c9175c3fcec2202e", + 1, + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 100, + "d02d72e067e77158444ef2020ff2d325f929b363", + 3, + ) +} + +func TestDecodeVerify(t *testing.T) { + // Altered signature, out of bounds r = n. + decodeExpectInvalid( + t, + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", + 0, + ) + // Altered signature, high s. + decodeExpectInvalid( + t, + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ca0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a0eb5a962cd82325b4d608b06c3f168d618b652f7440d8609ee6c4a37d10cff750", + 0, + ) + // Altered signature, s decreased by one. + decodeExpectFromMismatch( + t, + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f0", + 0, + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + ) +} diff --git a/analyzer/uncategorized/geth_sender_pub.go b/analyzer/uncategorized/geth_sender_pub.go new file mode 100644 index 000000000..2f6984a06 --- /dev/null +++ b/analyzer/uncategorized/geth_sender_pub.go @@ -0,0 +1,102 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains code from go-ethereum, adapted to recover a public key instead of an address. + +//nolint:gocritic,godot +package common + +import ( + "errors" + "math/big" + + ethCommon "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + ethCrypto "github.com/ethereum/go-ethereum/crypto" +) + +// LondonSenderPub is adapted from londonSigner.Sender +func LondonSenderPub(s ethTypes.Signer, tx *ethTypes.Transaction) ([]byte, error) { + if tx.Type() != ethTypes.DynamicFeeTxType { + return Eip2930SenderPub(s, tx) + } + V, R, S := tx.RawSignatureValues() + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.ChainID()) != 0 { + return nil, ethTypes.ErrInvalidChainId + } + return RecoverPlainPub(s.Hash(tx), R, S, V, true) +} + +// Eip2930SenderPub is adapted from eip2930Signer.Sender +func Eip2930SenderPub(s ethTypes.Signer, tx *ethTypes.Transaction) ([]byte, error) { + V, R, S := tx.RawSignatureValues() + switch tx.Type() { + case ethTypes.LegacyTxType: + if !tx.Protected() { + return HomesteadSenderPub(tx) + } + V = new(big.Int).Sub(V, new(big.Int).Mul(s.ChainID(), big.NewInt(2))) + V.Sub(V, big.NewInt(8)) + case ethTypes.AccessListTxType: + // AL txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + default: + return nil, ethTypes.ErrTxTypeNotSupported + } + if tx.ChainId().Cmp(s.ChainID()) != 0 { + return nil, ethTypes.ErrInvalidChainId + } + return RecoverPlainPub(s.Hash(tx), R, S, V, true) +} + +// HomesteadSenderPub is adapted from HomesteadSigner.Sender +func HomesteadSenderPub(tx *ethTypes.Transaction) ([]byte, error) { + if tx.Type() != ethTypes.LegacyTxType { + return nil, ethTypes.ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return RecoverPlainPub(ethTypes.HomesteadSigner{}.Hash(tx), r, s, v, true) +} + +// RecoverPlainPub is adapted from recoverPlain +func RecoverPlainPub(sighash ethCommon.Hash, R, S, Vb *big.Int, homestead bool) ([]byte, error) { + if Vb.BitLen() > 8 { + return nil, ethTypes.ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !ethCrypto.ValidateSignatureValues(V, R, S, homestead) { + return nil, ethTypes.ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, ethCrypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + // recover the public key from the signature + pub, err := ethCrypto.Ecrecover(sighash[:], sig) + if err != nil { + return nil, err + } + if len(pub) == 0 || pub[0] != 4 { + return nil, errors.New("invalid public key") + } + return pub, nil +} diff --git a/analyzer/uncategorized/helpers.go b/analyzer/uncategorized/helpers.go new file mode 100644 index 000000000..73f807fbf --- /dev/null +++ b/analyzer/uncategorized/helpers.go @@ -0,0 +1,18 @@ +package common + +import ( + "fmt" + "io" +) + +func CloseOrLog(c io.Closer) { + if err := c.Close(); err != nil { + fmt.Printf("close: %v", err) + } +} + +func WriteOrLog(w io.Writer, p []byte) { + if _, err := w.Write(p); err != nil { + fmt.Printf("write: %v", err) + } +} diff --git a/analyzer/uncategorized/quantities.go b/analyzer/uncategorized/quantities.go new file mode 100644 index 000000000..9f54337ab --- /dev/null +++ b/analyzer/uncategorized/quantities.go @@ -0,0 +1,19 @@ +package common + +import ( + "fmt" + "math/big" + + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +func StringifyNativeDenomination(amount *sdkTypes.BaseUnits) (string, error) { + if amount.Denomination != sdkTypes.NativeDenomination { + return "", fmt.Errorf("denomination '%s' expecting native denomination '%s'", amount.Denomination, sdkTypes.NativeDenomination) + } + return amount.Amount.String(), nil +} + +func StringifyBytes(value []byte) string { + return new(big.Int).SetBytes(value).String() +} diff --git a/analyzer/uncategorized/runtimes.go b/analyzer/uncategorized/runtimes.go new file mode 100644 index 000000000..8f980323b --- /dev/null +++ b/analyzer/uncategorized/runtimes.go @@ -0,0 +1,29 @@ +package common + +import ( + "fmt" + + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +func VerifyUtx(sigContext signature.Context, utx *sdkTypes.UnverifiedTransaction) (*sdkTypes.Transaction, error) { + if len(utx.AuthProofs) == 1 && utx.AuthProofs[0].Module != "" { + switch utx.AuthProofs[0].Module { + case "evm.ethereum.v0": + tx, err := decodeEthRawTx(utx.Body, 42262) + if err != nil { + return nil, err + } + return tx, nil + default: + return nil, fmt.Errorf("module-controlled decoding scheme %s not supported", utx.AuthProofs[0].Module) + } + } else { + tx, err := utx.Verify(sigContext) + if err != nil { + return nil, fmt.Errorf("verify: %w", err) + } + return tx, nil + } +} diff --git a/analyzer/uncategorized/visitors.go b/analyzer/uncategorized/visitors.go new file mode 100644 index 000000000..b7d6d5a53 --- /dev/null +++ b/analyzer/uncategorized/visitors.go @@ -0,0 +1,208 @@ +package common + +import ( + "bytes" + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/accounts" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/core" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + sdkTypes "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +type CallHandler struct { + AccountsTransfer func(body *accounts.Transfer) error + ConsensusAccountsDeposit func(body *consensusaccounts.Deposit) error + ConsensusAccountsWithdraw func(body *consensusaccounts.Withdraw) error + EvmCreate func(body *evm.Create, ok *[]byte) error + EvmCall func(body *evm.Call, ok *[]byte) error +} + +//nolint:nestif +func VisitCall(call *sdkTypes.Call, result *sdkTypes.CallResult, handler *CallHandler) error { + switch call.Method { + case "accounts.Transfer": + if handler.AccountsTransfer != nil { + var body accounts.Transfer + if err := cbor.Unmarshal(call.Body, &body); err != nil { + return fmt.Errorf("unmarshal accounts transfer: %w", err) + } + if err := handler.AccountsTransfer(&body); err != nil { + return fmt.Errorf("accounts transfer: %w", err) + } + } + case "consensus.Deposit": + if handler.ConsensusAccountsDeposit != nil { + var body consensusaccounts.Deposit + if err := cbor.Unmarshal(call.Body, &body); err != nil { + return fmt.Errorf("unmarshal consensus accounts deposit: %w", err) + } + if err := handler.ConsensusAccountsDeposit(&body); err != nil { + return fmt.Errorf("consensus accounts deposit: %w", err) + } + } + case "consensus.Withdraw": + if handler.ConsensusAccountsWithdraw != nil { + var body consensusaccounts.Withdraw + if err := cbor.Unmarshal(call.Body, &body); err != nil { + return fmt.Errorf("unmarshal consensus accounts withdraw: %w", err) + } + if err := handler.ConsensusAccountsWithdraw(&body); err != nil { + return fmt.Errorf("consensus accounts withdraw: %w", err) + } + } + case "evm.Create": + if handler.EvmCreate != nil { + var body evm.Create + if err := cbor.Unmarshal(call.Body, &body); err != nil { + return fmt.Errorf("unmarshal evm create: %w", err) + } + var okP *[]byte + if !result.IsUnknown() && result.IsSuccess() { + var ok []byte + if err := cbor.Unmarshal(result.Ok, &ok); err != nil { + return fmt.Errorf("unmarshal evm create result: %w", err) + } + okP = &ok + } + if err := handler.EvmCreate(&body, okP); err != nil { + return fmt.Errorf("evm create: %w", err) + } + } + case "evm.Call": + if handler.EvmCall != nil { + var body evm.Call + if err := cbor.Unmarshal(call.Body, &body); err != nil { + return fmt.Errorf("unmarshal evm call: %w", err) + } + var okP *[]byte + if !result.IsUnknown() && result.IsSuccess() { + var ok []byte + if err := cbor.Unmarshal(result.Ok, &ok); err != nil { + return fmt.Errorf("unmarshal evm create result: %w", err) + } + okP = &ok + } + if err := handler.EvmCall(&body, okP); err != nil { + return fmt.Errorf("evm call: %w", err) + } + } + } + return nil +} + +type SdkEventHandler struct { + Core func(event *core.Event) error + Accounts func(event *accounts.Event) error + ConsensusAccounts func(event *consensusaccounts.Event) error + Evm func(event *evm.Event) error +} + +func VisitSdkEvent(event *sdkTypes.Event, handler *SdkEventHandler) error { + if handler.Core != nil { + coreEvents, err := core.DecodeEvent(event) + if err != nil { + return fmt.Errorf("decode core: %w", err) + } + for i, coreEvent := range coreEvents { + coreEventCast, ok := coreEvent.(*core.Event) + if !ok { + return fmt.Errorf("decoded event %d could not cast to core.Event", i) + } + if err = handler.Core(coreEventCast); err != nil { + return fmt.Errorf("decoded event %d core: %w", i, err) + } + } + } + if handler.Accounts != nil { + accountEvents, err := accounts.DecodeEvent(event) + if err != nil { + return fmt.Errorf("decode accounts: %w", err) + } + for i, accountEvent := range accountEvents { + accountEventCast, ok := accountEvent.(*accounts.Event) + if !ok { + return fmt.Errorf("decoded event %d could not cast to accounts.Event", i) + } + if err = handler.Accounts(accountEventCast); err != nil { + return fmt.Errorf("decoded event %d accounts: %w", i, err) + } + } + } + if handler.ConsensusAccounts != nil { + consensusAccountsEvents, err := consensusaccounts.DecodeEvent(event) + if err != nil { + return fmt.Errorf("decode consensus accounts: %w", err) + } + for i, consensusAccountsEvent := range consensusAccountsEvents { + consensusAccountsEventCast, ok := consensusAccountsEvent.(*consensusaccounts.Event) + if !ok { + return fmt.Errorf("decoded event %d could not cast to consensusaccounts.Event", i) + } + if err = handler.ConsensusAccounts(consensusAccountsEventCast); err != nil { + return fmt.Errorf("decoded event %d consensus accounts: %w", i, err) + } + } + } + if handler.Evm != nil { + evmEvents, err := evm.DecodeEvent(event) + if err != nil { + return fmt.Errorf("decode evm: %w", err) + } + for i, evmEvent := range evmEvents { + evmEventCast, ok := evmEvent.(*evm.Event) + if !ok { + return fmt.Errorf("decoded event %d could not cast to evm.Event", i) + } + if err = handler.Evm(evmEventCast); err != nil { + return fmt.Errorf("decoded event %d evm: %w", i, err) + } + } + } + return nil +} + +func VisitSdkEvents(events []*sdkTypes.Event, handler *SdkEventHandler) error { + for i, event := range events { + if err := VisitSdkEvent(event, handler); err != nil { + return fmt.Errorf("event %d: %w", i, err) + } + } + return nil +} + +type EvmEventHandler struct { + Erc20Transfer func(fromEthAddr []byte, toEthAddr []byte, amountU256 []byte) error + Erc20Approval func(ownerEthAddr []byte, spenderEthAddr []byte, amountU256 []byte) error +} + +func VisitEvmEvent(event *evm.Event, handler *EvmEventHandler) error { + if len(event.Topics) == 0 { + return nil + } + switch { + case bytes.Equal(event.Topics[0], TopicErc20Transfer) && len(event.Topics) == 3: + if handler.Erc20Transfer != nil { + if err := handler.Erc20Transfer( + SliceEthAddress(event.Topics[1]), + SliceEthAddress(event.Topics[2]), + event.Data, + ); err != nil { + return fmt.Errorf("erc20 transfer: %w", err) + } + } + case bytes.Equal(event.Topics[0], TopicErc20Approval) && len(event.Topics) == 3: + if handler.Erc20Approval != nil { + if err := handler.Erc20Approval( + SliceEthAddress(event.Topics[1]), + SliceEthAddress(event.Topics[2]), + event.Data, + ); err != nil { + return fmt.Errorf("erc20 approval: %w", err) + } + } + } + return nil +} diff --git a/go.mod b/go.mod index d1024fc06..7d16cb4d3 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace github.com/tendermint/tendermint => github.com/oasisprotocol/tendermint require ( github.com/cockroachdb/cockroach-go/v2 v2.2.8 github.com/dgraph-io/ristretto v0.1.0 + github.com/ethereum/go-ethereum v1.10.19 github.com/go-chi/chi/v5 v5.0.7 github.com/go-kit/log v0.2.1 github.com/golang-migrate/migrate/v4 v4.15.2 @@ -27,10 +28,12 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/eapache/channels v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect @@ -93,6 +96,7 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/sergi/go-diff v1.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -120,5 +124,5 @@ require ( require ( github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991920 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e ) diff --git a/go.sum b/go.sum index a91871ab9..6c6417923 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -186,6 +188,8 @@ github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -379,6 +383,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= @@ -386,7 +394,6 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -439,6 +446,8 @@ github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPO github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/ethereum/go-ethereum v1.10.19 h1:EOR5JbL4MD5yeOqv8W2iC1s4NximrTjqFccUz8lyBRA= +github.com/ethereum/go-ethereum v1.10.19/go.mod h1:IJBNMtzKcNHPtllYihy6BL2IgK1u+32JriaTbdt4v+w= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -510,6 +519,7 @@ github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -526,6 +536,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -952,6 +963,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= @@ -1040,6 +1052,7 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1163,6 +1176,7 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -1194,10 +1208,12 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -1268,8 +1284,11 @@ github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= diff --git a/storage/migrations/0011_emerald_incoming.up.sql b/storage/migrations/0011_emerald_incoming.up.sql new file mode 100644 index 000000000..bbd0b13e2 --- /dev/null +++ b/storage/migrations/0011_emerald_incoming.up.sql @@ -0,0 +1,57 @@ +BEGIN; + +CREATE TABLE block_extra ( + chain_alias VARCHAR(32), + height BIGINT, + b_hash CHAR(64), + num_transactions INTEGER, + gas_used BIGINT, + size INTEGER, + PRIMARY KEY (chain_alias, height) +); +CREATE UNIQUE INDEX block_extra_hash ON block_extra (b_hash); + +CREATE TABLE transaction_extra ( + chain_alias VARCHAR(32), + height BIGINT, + tx_index INTEGER, + tx_hash CHAR(64), + eth_hash CHAR(64), + PRIMARY KEY (chain_alias, height, tx_index) +); +CREATE INDEX transaction_extra_hash ON transaction_extra (tx_hash); +CREATE INDEX transaction_extra_eth_hash ON transaction_extra (eth_hash); + +CREATE TABLE transaction_signer ( + chain_alias VARCHAR(32), + height BIGINT, + tx_index INTEGER, + signer_index INTEGER, + addr VARCHAR(46), + nonce INTEGER, + PRIMARY KEY (chain_alias, height, tx_index, signer_index) +); +CREATE INDEX transaction_signer_chain_alias_signer_addr_nonce ON transaction_signer (chain_alias, addr, nonce); + +CREATE TABLE related_transaction ( + chain_alias VARCHAR(32), + account_address VARCHAR(46), + tx_height BIGINT, + tx_index INTEGER +); +CREATE INDEX related_transaction_chain_alias_account_address ON related_transaction (chain_alias, account_address); + +CREATE TABLE address_preimage ( + address VARCHAR(46) PRIMARY KEY, + context_identifier VARCHAR(64), + context_version INTEGER, + addr_data bytea +); + +CREATE TABLE progress ( + chain_alias VARCHAR(32) PRIMARY KEY, + first_unscanned_height BIGINT +); +INSERT INTO progress (chain_alias, first_unscanned_height) VALUES ('mainnet_emerald', 2929940); + +COMMIT;