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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Andrew7234/api tests #360

Merged
merged 27 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
201deb1
nodeapi: Store raw oasis-core in serialized form
mitjat Apr 13, 2023
b8d7a81
method-based cached ConsensusApiLite
Andrew7234 Mar 27, 2023
0c80484
wip: unit test to dump state
Andrew7234 Mar 28, 2023
dac3917
switch encoding/gob -> encoding/json
Andrew7234 Mar 28, 2023
719c141
smoother test flow
Andrew7234 Mar 31, 2023
4ea13d2
nits
Andrew7234 Mar 31, 2023
414666c
working api-test flow
Andrew7234 Apr 3, 2023
c67d8c6
address comments
Andrew7234 Apr 7, 2023
1fa8e7f
add runtime file backend
Andrew7234 Apr 7, 2023
daeb908
change runtime api methods to return slices of values
Andrew7234 Apr 7, 2023
ca22d12
update config with file-based cache; switch encoding to cbor
Andrew7234 Apr 12, 2023
5e78d3c
remove data files and unnecessary tests that are now part of the make…
Andrew7234 Apr 12, 2023
46d8b95
switch encoding to cbor; deserialize to explicit type
Andrew7234 Apr 16, 2023
02df003
remove json annotations
Andrew7234 Apr 18, 2023
9292d28
check for latestHeight; do not connect to node for pure file-based co…
Andrew7234 Apr 18, 2023
461e835
move api tests -> e2e_regression
Andrew7234 Apr 18, 2023
a899da8
nits
Andrew7234 Apr 20, 2023
5c975c2
Factor out cache handling in FileConsensusApiLite
mitjat Apr 25, 2023
172bac7
Avoid marshal+unmarshall on every uncached call; serde is a CPU bottl…
mitjat Apr 25, 2023
8cd781c
Remove unnecessary params
mitjat Apr 25, 2023
5a40bba
Create KVStore type
mitjat Apr 25, 2023
d2e5479
pogreb constructor: rename filename->cacheDir
mitjat Apr 25, 2023
671130f
Move volatile call detection (HeightLatest) out of cache logic
mitjat Apr 25, 2023
f6953a9
nodeapi/file/runtime.go: Use KVStore, abstract away duplicated code
mitjat Apr 25, 2023
1179568
address comments
Andrew7234 Apr 25, 2023
91564bc
nits
Andrew7234 Apr 26, 2023
8ff116c
linter
Andrew7234 Apr 26, 2023
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
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ test-e2e: export OASIS_INDEXER_E2E = true
test-e2e:
@$(GO) test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic -v ./tests/e2e

fill-cache-for-e2e-regression: oasis-indexer
cp tests/e2e_regression/e2e_config.yml /tmp/indexer_fill_e2e_regression_cache.yml
sed -E -i='' 's/query_on_cache_miss: false/query_on_cache_miss: true/g' /tmp/indexer_fill_e2e_regression_cache.yml
./oasis-indexer --config /tmp/indexer_fill_e2e_regression_cache.yml analyze

# Run the api tests locally, assuming the environment is set up with an oasis-node that is
# accessible as specified in the config file.
test-e2e-regression: oasis-indexer
./oasis-indexer --config tests/e2e_regression/e2e_config.yml analyze
@$(ECHO) "$(CYAN)*** Indexer finished; starting api tests...$(OFF)"
./tests/e2e_regression/run.sh

# Format code.
fmt:
@$(ECHO) "$(CYAN)*** Running Go formatters...$(OFF)"
Expand Down
10 changes: 5 additions & 5 deletions analyzer/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type EventType = apiTypes.ConsensusEventType // alias for brevity

type parsedEvent struct {
ty EventType
body interface{}
rawBodyJSON json.RawMessage
relatedAddresses []staking.Address
}

Expand Down Expand Up @@ -482,7 +482,7 @@ func (m *Main) queueTxEventInserts(batch *storage.QueryBatch, data *storage.Cons
eventData := m.extractEventData(event)
txAccounts = append(txAccounts, eventData.relatedAddresses...)
accounts := extractUniqueAddresses(eventData.relatedAddresses)
body, err := json.Marshal(eventData.body)
body, err := json.Marshal(eventData.rawBodyJSON)
if err != nil {
return err
}
Expand Down Expand Up @@ -915,7 +915,7 @@ func (m *Main) queueGovernanceEventInserts(batch *storage.QueryBatch, data *stor

func (m *Main) queueSingleEventInserts(batch *storage.QueryBatch, eventData *parsedEvent, height int64) error {
accounts := extractUniqueAddresses(eventData.relatedAddresses)
body, err := json.Marshal(eventData.body)
body, err := json.Marshal(eventData.rawBodyJSON)
if err != nil {
return err
}
Expand Down Expand Up @@ -950,8 +950,8 @@ func extractUniqueAddresses(accounts []staking.Address) []string {
// extractEventData extracts the type, the body (JSON-serialized), and the related accounts of an event.
func (m *Main) extractEventData(event nodeapi.Event) parsedEvent {
eventData := parsedEvent{
ty: event.Type,
body: event.Body,
ty: event.Type,
rawBodyJSON: event.RawBodyJSON,
}

// Fill in related accounts.
Expand Down
10 changes: 5 additions & 5 deletions analyzer/runtime/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func registerTokenDecrease(tokenChanges map[TokenChangeKey]*big.Int, contractAdd
change.Sub(change, amount)
}

func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []*nodeapi.RuntimeTransactionWithResults, rawEvents []*nodeapi.RuntimeEvent, logger *log.Logger) (*BlockData, error) { //nolint:gocyclo
func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []nodeapi.RuntimeTransactionWithResults, rawEvents []nodeapi.RuntimeEvent, logger *log.Logger) (*BlockData, error) { //nolint:gocyclo
Copy link
Collaborator

Choose a reason for hiding this comment

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

what did these * removals do? originally they were pointers so that we don't have to copy structs around. not that that's particularly expensive.

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh it's so that we don't need a zillion different GetFromCacheOrCall variants, right?

blockData := BlockData{
Header: blockHeader,
NumTransactions: len(txrs),
Expand All @@ -260,7 +260,7 @@ func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []*nodeapi.Runtim
}

// Extract info from non-tx events.
rawNonTxEvents := []*nodeapi.RuntimeEvent{}
rawNonTxEvents := []nodeapi.RuntimeEvent{}
for _, e := range rawEvents {
if e.TxHash.String() == util.ZeroTxHash {
rawNonTxEvents = append(rawNonTxEvents, e)
Expand Down Expand Up @@ -394,9 +394,9 @@ func ExtractRound(blockHeader nodeapi.RuntimeBlockHeader, txrs []*nodeapi.Runtim
}
blockTransactionData.Amount = common.Ptr(common.BigIntFromQuantity(amount))
}
txEvents := make([]*nodeapi.RuntimeEvent, len(txr.Events))
txEvents := make([]nodeapi.RuntimeEvent, len(txr.Events))
for i, e := range txr.Events {
txEvents[i] = (*nodeapi.RuntimeEvent)(e)
txEvents[i] = (nodeapi.RuntimeEvent)(*e)
}
extractedTxEvents, err := extractEvents(&blockData, blockTransactionData.RelatedAccountAddresses, txEvents)
if err != nil {
Expand Down Expand Up @@ -452,7 +452,7 @@ func sumGasUsed(events []*EventData) (sum uint64, foundGasUsedEvent bool) {
return
}

func extractEvents(blockData *BlockData, relatedAccountAddresses map[apiTypes.Address]bool, eventsRaw []*nodeapi.RuntimeEvent) ([]*EventData, error) {
func extractEvents(blockData *BlockData, relatedAccountAddresses map[apiTypes.Address]bool, eventsRaw []nodeapi.RuntimeEvent) ([]*EventData, error) {
extractedEvents := []*EventData{}
if err := VisitSdkEvents(eventsRaw, &SdkEventHandler{
Core: func(event *core.Event) error {
Expand Down
6 changes: 3 additions & 3 deletions analyzer/runtime/visitors.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ func VisitSdkEvent(event *nodeapi.RuntimeEvent, handler *SdkEventHandler) error
return nil
}

func VisitSdkEvents(events []*nodeapi.RuntimeEvent, handler *SdkEventHandler) error {
for i, event := range events {
if err := VisitSdkEvent(event, handler); err != nil {
func VisitSdkEvents(events []nodeapi.RuntimeEvent, handler *SdkEventHandler) error {
for i := range events {
if err := VisitSdkEvent(&events[i], handler); err != nil {
return fmt.Errorf("event %d: %w", i, err)
}
}
Expand Down
11 changes: 11 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"encoding/json"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -90,6 +91,16 @@ func Ptr[T any](v T) *T {
return &v
}

// Returns `v` as a JSON string. If `v` cannot be marshaled,
// returns the string "null" instead.
func TryAsJSON(v interface{}) json.RawMessage {
Copy link
Collaborator

Choose a reason for hiding this comment

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

didn't this appear in an earlier PR? what happened to that? now I can't find it

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh that was #382, turns out it never got merged

encoded, err := json.Marshal(v)
if err != nil {
return json.RawMessage("null")
}
return json.RawMessage(encoded)
}

// Key used to set values in a web request context. API uses this to set
// values, backend uses this to retrieve values.
type ContextKey string
Expand Down
19 changes: 19 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ type AnalyzersList struct {

// SourceConfig has some controls about what chain we're analyzing and how to connect.
type SourceConfig struct {
// Cache holds the configuration for a file-based caching backend.
Cache *CacheConfig `koanf:"cache"`

// ChainName is the name of the chain (e.g. mainnet/testnet). Set
// this to use one of the default chains.
ChainName string `koanf:"chain_name"`
Expand Down Expand Up @@ -168,6 +171,22 @@ func SingleNetworkLookup(rpc string) map[string]*NodeConfig {
}
}

type CacheConfig struct {
// CacheDir is the directory where the cache data is stored
CacheDir string `koanf:"cache_dir"`

// If set, the indexer will query the node upon any cache
// misses.
QueryOnCacheMiss bool `koanf:"query_on_cache_miss"`
Andrew7234 marked this conversation as resolved.
Show resolved Hide resolved
}

func (cfg *CacheConfig) Validate() error {
if cfg.CacheDir == "" {
return fmt.Errorf("invalid cache filepath")
}
return nil
}

type CustomChainConfig struct {
// History is the sequence of networks in the chain.
History *History `koanf:"history"`
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ require (
)

require (
github.com/akrylysov/pogreb v0.10.1
github.com/deepmap/oapi-codegen v1.12.4
github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991920
github.com/rs/cors v1.8.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO
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/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand Down
4 changes: 2 additions & 2 deletions storage/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ type RuntimeSourceStorage interface {
type RuntimeAllData struct {
Round uint64
BlockHeader nodeapi.RuntimeBlockHeader
RawEvents []*nodeapi.RuntimeEvent
TransactionsWithResults []*nodeapi.RuntimeTransactionWithResults
RawEvents []nodeapi.RuntimeEvent
TransactionsWithResults []nodeapi.RuntimeTransactionWithResults
}

// TransactionWithResults contains a verified transaction, and the results of
Expand Down
40 changes: 40 additions & 0 deletions storage/oasis/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ package oasis
import (
"context"
"fmt"
"path/filepath"

"github.com/oasisprotocol/oasis-indexer/common"
"github.com/oasisprotocol/oasis-indexer/config"
"github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi"
"github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi/file"
"github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi/history"
)

Expand All @@ -17,10 +20,32 @@ const (

// NewConsensusClient creates a new ConsensusClient.
func NewConsensusClient(ctx context.Context, sourceConfig *config.SourceConfig) (*ConsensusClient, error) {
// If we are using purely file-backed indexer, do not connect to the node.
if sourceConfig.Cache != nil && !sourceConfig.Cache.QueryOnCacheMiss {
cachePath := filepath.Join(sourceConfig.Cache.CacheDir, "consensus")
nodeApi, err := file.NewFileConsensusApiLite(cachePath, nil)
if err != nil {
return nil, fmt.Errorf("error instantiating cache-based consensusApi: %w", err)
}
return &ConsensusClient{
nodeApi: nodeApi,
network: sourceConfig.SDKNetwork(),
}, nil
}

// Create an API that connects to the real node, then wrap it in a caching layer.
var nodeApi nodeapi.ConsensusApiLite
Andrew7234 marked this conversation as resolved.
Show resolved Hide resolved
nodeApi, err := history.NewHistoryConsensusApiLite(ctx, sourceConfig.History(), sourceConfig.Nodes, sourceConfig.FastStartup)
if err != nil {
return nil, fmt.Errorf("instantiating history consensus API lite: %w", err)
}
if sourceConfig.Cache != nil {
cachePath := filepath.Join(sourceConfig.Cache.CacheDir, "consensus")
nodeApi, err = file.NewFileConsensusApiLite(cachePath, nodeApi)
if err != nil {
return nil, fmt.Errorf("error instantiating cache-based consensusApi: %w", err)
}
}
return &ConsensusClient{
nodeApi: nodeApi,
network: sourceConfig.SDKNetwork(),
Expand All @@ -30,11 +55,26 @@ func NewConsensusClient(ctx context.Context, sourceConfig *config.SourceConfig)
// NewRuntimeClient creates a new RuntimeClient.
func NewRuntimeClient(ctx context.Context, sourceConfig *config.SourceConfig, runtime common.Runtime) (*RuntimeClient, error) {
sdkPT := sourceConfig.SDKNetwork().ParaTimes.All[runtime.String()]
var nodeApi nodeapi.RuntimeApiLite
nodeApi, err := history.NewHistoryRuntimeApiLite(ctx, sourceConfig.History(), sdkPT, sourceConfig.Nodes, sourceConfig.FastStartup, runtime)
if err != nil {
return nil, fmt.Errorf("instantiating history runtime API lite: %w", err)
}

// todo: short circuit if using purely a file-based backend and avoid connecting
Andrew7234 marked this conversation as resolved.
Show resolved Hide resolved
// to the node at all. this requires storing runtime info offline.
if sourceConfig.Cache != nil {
cachePath := filepath.Join(sourceConfig.Cache.CacheDir, runtime.String())
if sourceConfig.Cache.QueryOnCacheMiss {
nodeApi, err = file.NewFileRuntimeApiLite(runtime.String(), cachePath, nodeApi)
} else {
nodeApi, err = file.NewFileRuntimeApiLite(runtime.String(), cachePath, nil)
}
if err != nil {
return nil, fmt.Errorf("error instantiating cache-based runtimeApi: %w", err)
}
}

return &RuntimeClient{
nodeApi: nodeApi,
sdkPT: sdkPT,
Expand Down
16 changes: 11 additions & 5 deletions storage/oasis/nodeapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nodeapi

import (
"context"
"encoding/json"
"time"

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
Expand Down Expand Up @@ -82,9 +83,14 @@ type Event struct {
Height int64
TxHash hash.Hash

// The fine-grained Event struct directly from oasis-core; corresponds to
// at most one of the fields below (StakingTransfer, StakingBurn, etc.).
Body interface{}
// The body of the Event struct as it was received from oasis-core. For most
// event types, a summary of the event (containing only indexer-relevant fields)
// will be present in one of the fields below (StakingTransfer, StakingBurn, etc.).
// For event types that the indexer doesn't process beyond logging, only this
// field will be populated.
// We convert to JSON and effectively erase the type here in order to decouple
// oasis-core types (which vary between versions) from the indexer.
RawBodyJSON json.RawMessage

// Called "Kind" in oasis-core but "Type" in indexer APIs and DBs.
Type apiTypes.ConsensusEventType
Expand Down Expand Up @@ -192,10 +198,10 @@ type Proposal governance.Proposal

// Like ConsensusApiLite, but for the runtime API.
type RuntimeApiLite interface {
GetEventsRaw(ctx context.Context, round uint64) ([]*RuntimeEvent, error)
GetEventsRaw(ctx context.Context, round uint64) ([]RuntimeEvent, error)
EVMSimulateCall(ctx context.Context, round uint64, gasPrice []byte, gasLimit uint64, caller []byte, address []byte, value []byte, data []byte) ([]byte, error)
GetBlockHeader(ctx context.Context, round uint64) (*RuntimeBlockHeader, error)
GetTransactionsWithResults(ctx context.Context, round uint64) ([]*RuntimeTransactionWithResults, error)
GetTransactionsWithResults(ctx context.Context, round uint64) ([]RuntimeTransactionWithResults, error)
}

type (
Expand Down