341 lines
7.6 KiB
Go
341 lines
7.6 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/cosmos/gogoproto/jsonpb"
|
|
gogoproto "github.com/cosmos/gogoproto/proto"
|
|
|
|
"cosmossdk.io/core/branch"
|
|
"cosmossdk.io/core/comet"
|
|
corecontext "cosmossdk.io/core/context"
|
|
"cosmossdk.io/core/event"
|
|
"cosmossdk.io/core/gas"
|
|
"cosmossdk.io/core/header"
|
|
"cosmossdk.io/core/server"
|
|
corestore "cosmossdk.io/core/store"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/server/v2/stf"
|
|
stfbranch "cosmossdk.io/server/v2/stf/branch"
|
|
stfgas "cosmossdk.io/server/v2/stf/gas"
|
|
)
|
|
|
|
var ErrInvalidMsgType = fmt.Errorf("invalid message type")
|
|
|
|
func (c cometServiceImpl) CometInfo(context.Context) comet.Info {
|
|
return comet.Info{}
|
|
}
|
|
|
|
// Services
|
|
|
|
var _ server.DynamicConfig = &dynamicConfigImpl{}
|
|
|
|
type dynamicConfigImpl struct {
|
|
homeDir string
|
|
}
|
|
|
|
func (d *dynamicConfigImpl) Get(key string) any {
|
|
return d.GetString(key)
|
|
}
|
|
|
|
func (d *dynamicConfigImpl) GetString(key string) string {
|
|
switch key {
|
|
case "home":
|
|
return d.homeDir
|
|
case "store.app-db-backend":
|
|
return "goleveldb"
|
|
case "server.minimum-gas-prices":
|
|
return "0stake"
|
|
default:
|
|
panic(fmt.Sprintf("unknown key: %s", key))
|
|
}
|
|
}
|
|
|
|
func (d *dynamicConfigImpl) UnmarshalSub(string, any) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
var _ comet.Service = &cometServiceImpl{}
|
|
|
|
type cometServiceImpl struct{}
|
|
|
|
type storeService struct {
|
|
actor []byte
|
|
executionService corestore.KVStoreService
|
|
}
|
|
|
|
type contextKeyType struct{}
|
|
|
|
var contextKey = contextKeyType{}
|
|
|
|
type integrationContext struct {
|
|
state corestore.WriterMap
|
|
gasMeter gas.Meter
|
|
header header.Info
|
|
events []event.Event
|
|
}
|
|
|
|
func SetHeaderInfo(ctx context.Context, h header.Info) context.Context {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return ctx
|
|
}
|
|
iCtx.header = h
|
|
return context.WithValue(ctx, contextKey, iCtx)
|
|
}
|
|
|
|
func HeaderInfoFromContext(ctx context.Context) header.Info {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if ok {
|
|
return iCtx.header
|
|
}
|
|
return header.Info{}
|
|
}
|
|
|
|
func SetCometInfo(ctx context.Context, c comet.Info) context.Context {
|
|
return context.WithValue(ctx, corecontext.CometInfoKey, c)
|
|
}
|
|
|
|
func EventsFromContext(ctx context.Context) []event.Event {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return iCtx.events
|
|
}
|
|
|
|
func GetAttributes(e []event.Event, key string) ([]event.Attribute, bool) {
|
|
attrs := make([]event.Attribute, 0)
|
|
for _, event := range e {
|
|
attributes, err := event.Attributes()
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
for _, attr := range attributes {
|
|
if attr.Key == key {
|
|
attrs = append(attrs, attr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return attrs, len(attrs) > 0
|
|
}
|
|
|
|
func GetAttribute(e event.Event, key string) (event.Attribute, bool) {
|
|
attributes, err := e.Attributes()
|
|
if err != nil {
|
|
return event.Attribute{}, false
|
|
}
|
|
for _, attr := range attributes {
|
|
if attr.Key == key {
|
|
return attr, true
|
|
}
|
|
}
|
|
|
|
return event.Attribute{}, false
|
|
}
|
|
|
|
func GasMeterFromContext(ctx context.Context) gas.Meter {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return iCtx.gasMeter
|
|
}
|
|
|
|
func GasMeterFactory(ctx context.Context) func() gas.Meter {
|
|
return func() gas.Meter {
|
|
return GasMeterFromContext(ctx)
|
|
}
|
|
}
|
|
|
|
func SetGasMeter(ctx context.Context, meter gas.Meter) context.Context {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return ctx
|
|
}
|
|
iCtx.gasMeter = meter
|
|
return context.WithValue(ctx, contextKey, iCtx)
|
|
}
|
|
|
|
func (s storeService) OpenKVStore(ctx context.Context) corestore.KVStore {
|
|
const gasLimit = 1_000_000
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return s.executionService.OpenKVStore(ctx)
|
|
}
|
|
|
|
iCtx.gasMeter = stfgas.NewMeter(gasLimit)
|
|
writerMap := stfgas.NewMeteredWriterMap(stfgas.DefaultConfig, iCtx.gasMeter, iCtx.state)
|
|
state, err := writerMap.GetWriter(s.actor)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return state
|
|
}
|
|
|
|
var (
|
|
_ event.Service = &eventService{}
|
|
_ event.Manager = &eventManager{}
|
|
)
|
|
|
|
type eventService struct{}
|
|
|
|
// EventManager implements event.Service.
|
|
func (e *eventService) EventManager(ctx context.Context) event.Manager {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
panic("context is not an integration context")
|
|
}
|
|
|
|
return &eventManager{ctx: iCtx}
|
|
}
|
|
|
|
type eventManager struct {
|
|
ctx *integrationContext
|
|
}
|
|
|
|
// Emit implements event.Manager.
|
|
func (e *eventManager) Emit(tev transaction.Msg) error {
|
|
ev := event.Event{
|
|
Type: gogoproto.MessageName(tev),
|
|
Attributes: func() ([]event.Attribute, error) {
|
|
outerEvent, err := stf.TypedEventToEvent(tev)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return outerEvent.Attributes()
|
|
},
|
|
Data: func() (json.RawMessage, error) {
|
|
buf := new(bytes.Buffer)
|
|
jm := &jsonpb.Marshaler{OrigName: true, EmitDefaults: true, AnyResolver: nil}
|
|
if err := jm.Marshal(buf, tev); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
},
|
|
}
|
|
|
|
e.ctx.events = append(e.ctx.events, ev)
|
|
return nil
|
|
}
|
|
|
|
// EmitKV implements event.Manager.
|
|
func (e *eventManager) EmitKV(eventType string, attrs ...event.Attribute) error {
|
|
ev := event.Event{
|
|
Type: eventType,
|
|
Attributes: func() ([]event.Attribute, error) {
|
|
return attrs, nil
|
|
},
|
|
Data: func() (json.RawMessage, error) {
|
|
return json.Marshal(attrs)
|
|
},
|
|
}
|
|
|
|
e.ctx.events = append(e.ctx.events, ev)
|
|
return nil
|
|
}
|
|
|
|
var _ branch.Service = &BranchService{}
|
|
|
|
// custom branch service for integration tests
|
|
type BranchService struct{}
|
|
|
|
func (bs *BranchService) Execute(ctx context.Context, f func(ctx context.Context) error) error {
|
|
_, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return errors.New("context is not an integration context")
|
|
}
|
|
|
|
return f(ctx)
|
|
}
|
|
|
|
func (bs *BranchService) ExecuteWithGasLimit(
|
|
ctx context.Context,
|
|
gasLimit uint64,
|
|
f func(ctx context.Context) error,
|
|
) (gasUsed uint64, err error) {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return 0, errors.New("context is not an integration context")
|
|
}
|
|
|
|
originalGasMeter := iCtx.gasMeter
|
|
|
|
iCtx.gasMeter = stfgas.DefaultGasMeter(gasLimit)
|
|
|
|
// execute branched, with predefined gas limit.
|
|
err = bs.execute(ctx, iCtx, f)
|
|
|
|
// restore original context
|
|
gasUsed = iCtx.gasMeter.Limit() - iCtx.gasMeter.Remaining()
|
|
_ = originalGasMeter.Consume(gasUsed, "execute-with-gas-limit")
|
|
iCtx.gasMeter = stfgas.DefaultGasMeter(originalGasMeter.Remaining())
|
|
|
|
return gasUsed, err
|
|
}
|
|
|
|
func (bs BranchService) execute(ctx context.Context, ictx *integrationContext, f func(ctx context.Context) error) error {
|
|
branchedState := stfbranch.DefaultNewWriterMap(ictx.state)
|
|
meteredBranchedState := stfgas.DefaultWrapWithGasMeter(ictx.gasMeter, branchedState)
|
|
|
|
branchedCtx := &integrationContext{
|
|
state: meteredBranchedState,
|
|
gasMeter: ictx.gasMeter,
|
|
header: ictx.header,
|
|
events: ictx.events,
|
|
}
|
|
|
|
newCtx := context.WithValue(ctx, contextKey, branchedCtx)
|
|
|
|
err := f(newCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = applyStateChanges(ictx.state, branchedCtx.state)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyStateChanges(dst, src corestore.WriterMap) error {
|
|
changes, err := src.GetStateChanges()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return dst.ApplyStateChanges(changes)
|
|
}
|
|
|
|
var _ header.Service = &HeaderService{}
|
|
|
|
type HeaderService struct{}
|
|
|
|
func (h *HeaderService) HeaderInfo(ctx context.Context) header.Info {
|
|
iCtx, ok := ctx.Value(contextKey).(*integrationContext)
|
|
if !ok {
|
|
return header.Info{}
|
|
}
|
|
return iCtx.header
|
|
}
|
|
|
|
var _ gas.Service = &GasService{}
|
|
|
|
type GasService struct{}
|
|
|
|
func (g *GasService) GasMeter(ctx context.Context) gas.Meter {
|
|
return GasMeterFromContext(ctx)
|
|
}
|
|
|
|
func (g *GasService) GasConfig(ctx context.Context) gas.GasConfig {
|
|
return gas.GasConfig{}
|
|
}
|