From 239a4e04074b518d764644b0649e85827a0365dc Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 18:00:44 +0200 Subject: [PATCH 1/7] fix(prolog): implement fine-grained gas metering for predicates This update enhances the gas metering mechanism by leveraging VM hooks to accurately track gas consumption for both built-in predicates and user-defined predicates. --- x/logic/interpreter/instrument.go | 127 ------------------ x/logic/interpreter/interpreter.go | 23 +++- x/logic/interpreter/registry.go | 35 +++-- .../keeper/features/bech32_address_2.feature | 4 +- x/logic/keeper/features/consult_1.feature | 4 +- .../keeper/features/current_output_1.feature | 10 +- x/logic/keeper/features/open_4.feature | 6 +- x/logic/keeper/interpreter.go | 116 +++++++++++----- 8 files changed, 131 insertions(+), 194 deletions(-) delete mode 100644 x/logic/interpreter/instrument.go diff --git a/x/logic/interpreter/instrument.go b/x/logic/interpreter/instrument.go deleted file mode 100644 index d9736577..00000000 --- a/x/logic/interpreter/instrument.go +++ /dev/null @@ -1,127 +0,0 @@ -package interpreter - -import ( - "github.com/ichiban/prolog/engine" -) - -type Invariant func(env *engine.Env) error - -// Instrument0 is a higher order function that given a 0arg-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -func Instrument0(invariant Invariant, p engine.Predicate0) engine.Predicate0 { - return func(vm *engine.VM, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, cont, env) - } -} - -// Instrument1 is a higher order function that given a 1arg-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -func Instrument1(invariant Invariant, p engine.Predicate1) engine.Predicate1 { - return func(vm *engine.VM, t1 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, cont, env) - } -} - -// Instrument2 is a higher order function that given a 2args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -func Instrument2(invariant Invariant, p engine.Predicate2) engine.Predicate2 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, cont, env) - } -} - -// Instrument3 is a higher order function that given a 3args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -func Instrument3(invariant Invariant, p engine.Predicate3) engine.Predicate3 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, cont engine.Cont, - env *engine.Env, - ) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, cont, env) - } -} - -// Instrument4 is a higher order function that given a 4args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -// -//nolint:lll -func Instrument4(invariant Invariant, p engine.Predicate4) engine.Predicate4 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, t4 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, t4, cont, env) - } -} - -// Instrument5 is a higher order function that given a 5args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -// -//nolint:lll -func Instrument5(invariant Invariant, p engine.Predicate5) engine.Predicate5 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, t4 engine.Term, t5 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, t4, t5, cont, env) - } -} - -// Instrument6 is a higher order function that given a 6args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -// -//nolint:lll -func Instrument6(invariant Invariant, p engine.Predicate6) engine.Predicate6 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, t4 engine.Term, t5 engine.Term, t6 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, t4, t5, t6, cont, env) - } -} - -// Instrument7 is a higher order function that given a 7args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -// -//nolint:lll -func Instrument7(invariant Invariant, p engine.Predicate7) engine.Predicate7 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, t4 engine.Term, t5 engine.Term, t6 engine.Term, t7 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, t4, t5, t6, t7, cont, env) - } -} - -// Instrument8 is a higher order function that given a 8args-predicate and an invariant returns a new predicate that calls the -// invariant before calling the predicate. -// -//nolint:lll -func Instrument8(invariant Invariant, p engine.Predicate8) engine.Predicate8 { - return func(vm *engine.VM, t1 engine.Term, t2 engine.Term, t3 engine.Term, t4 engine.Term, t5 engine.Term, t6 engine.Term, t7 engine.Term, t8 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - if err := invariant(env); err != nil { - return engine.Error(err) - } - - return p(vm, t1, t2, t3, t4, t5, t6, t7, t8, cont, env) - } -} diff --git a/x/logic/interpreter/interpreter.go b/x/logic/interpreter/interpreter.go index 91bae510..715f20a4 100644 --- a/x/logic/interpreter/interpreter.go +++ b/x/logic/interpreter/interpreter.go @@ -17,10 +17,10 @@ type Option func(*prolog.Interpreter) error // WithPredicates configures the interpreter to register the specified predicates. // See WithPredicate for more details. -func WithPredicates(ctx goctx.Context, predicates []string, hook Hook) Option { +func WithPredicates(ctx goctx.Context, predicates []string) Option { return func(i *prolog.Interpreter) error { for _, predicate := range predicates { - if err := WithPredicate(ctx, predicate, hook)(i); err != nil { + if err := WithPredicate(ctx, predicate)(i); err != nil { return err } } @@ -28,14 +28,12 @@ func WithPredicates(ctx goctx.Context, predicates []string, hook Hook) Option { } } -// WithPredicate configures the interpreter to register the specified predicate with the specified hook. -// The hook is a function that is called before the predicate is executed and can be used to check some conditions, -// like the gas consumption or the permission to execute the predicate. +// WithPredicate configures the interpreter to register the specified predicate. // // The predicates names must be present in the registry, otherwise the function will return an error. -func WithPredicate(_ goctx.Context, predicate string, hook Hook) Option { +func WithPredicate(_ goctx.Context, predicate string) Option { return func(i *prolog.Interpreter) error { - if err := Register(i, predicate, hook); err != nil { + if err := Register(i, predicate); err != nil { return fmt.Errorf("error registering predicate '%s': %w", predicate, err) } return nil @@ -70,6 +68,7 @@ func WithFS(fs fs.FS) Option { } } +// WithMaxVariables configures the interpreter to use the specified maximum number of variables. func WithMaxVariables(maxVariables *math.Uint) Option { return func(i *prolog.Interpreter) error { if maxVariables != nil { @@ -81,6 +80,16 @@ func WithMaxVariables(maxVariables *math.Uint) Option { } } +// WithHooks configures the interpreter to use the specified hooks. +func WithHooks(hooks ...engine.HookFunc) Option { + return func(i *prolog.Interpreter) error { + i.InstallHook( + engine.CompositeHook(hooks...), + ) + return nil + } +} + // New creates a new prolog.Interpreter with the specified options. func New( opts ...Option, diff --git a/x/logic/interpreter/registry.go b/x/logic/interpreter/registry.go index 23f3e2ef..14264a4a 100644 --- a/x/logic/interpreter/registry.go +++ b/x/logic/interpreter/registry.go @@ -131,16 +131,17 @@ var RegistryNames = func() []string { return names }() -type Hook = func(functor string) func(env *engine.Env) error +// IsRegistered returns true if the predicate with the given name is registered in the interpreter. +// Registered predicates are built-in predicates that are available in the interpreter. +func IsRegistered(name string) bool { + _, ok := registry.Get(name) + return ok +} -// Register registers a well-known predicate in the interpreter with support for consumption measurement. -// name is the name of the predicate in the form of "atom/arity". -// cost is the cost of executing the predicate. -// meter is the gas meter object that is called when the predicate is called and which allows to count the cost of -// executing the predicate(ctx). +// Register registers a well-known predicate in the interpreter. // //nolint:lll -func Register(i *prolog.Interpreter, name string, hook Hook) error { +func Register(i *prolog.Interpreter, name string) error { if p, ok := registry.Get(name); ok { parts := strings.Split(name, "/") if len(parts) == 2 { @@ -150,27 +151,25 @@ func Register(i *prolog.Interpreter, name string, hook Hook) error { return err } - invariant := hook(name) - switch arity { case 0: - i.Register0(atom, Instrument0(invariant, p.(func(*engine.VM, engine.Cont, *engine.Env) *engine.Promise))) + i.Register0(atom, p.(func(*engine.VM, engine.Cont, *engine.Env) *engine.Promise)) case 1: - i.Register1(atom, Instrument1(invariant, p.(func(*engine.VM, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register1(atom, p.(func(*engine.VM, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 2: - i.Register2(atom, Instrument2(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register2(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 3: - i.Register3(atom, Instrument3(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register3(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 4: - i.Register4(atom, Instrument4(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register4(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 5: - i.Register5(atom, Instrument5(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register5(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 6: - i.Register6(atom, Instrument6(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register6(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 7: - i.Register7(atom, Instrument7(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register7(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) case 8: - i.Register8(atom, Instrument8(invariant, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise))) + i.Register8(atom, p.(func(*engine.VM, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Term, engine.Cont, *engine.Env) *engine.Promise)) default: panic(fmt.Sprintf("unsupported arity: %s", name)) } diff --git a/x/logic/keeper/features/bech32_address_2.feature b/x/logic/keeper/features/bech32_address_2.feature index f2672993..ee31f978 100644 --- a/x/logic/keeper/features/bech32_address_2.feature +++ b/x/logic/keeper/features/bech32_address_2.feature @@ -109,7 +109,7 @@ Feature: bech32_address/2 Then the answer we get is: """ yaml height: 42 - gas_used: 4140 + gas_used: 4141 answer: has_more: false results: @@ -130,7 +130,7 @@ Feature: bech32_address/2 Then the answer we get is: """ yaml height: 42 - gas_used: 4140 + gas_used: 4141 answer: has_more: false results: diff --git a/x/logic/keeper/features/consult_1.feature b/x/logic/keeper/features/consult_1.feature index 05b95d85..801d38b6 100644 --- a/x/logic/keeper/features/consult_1.feature +++ b/x/logic/keeper/features/consult_1.feature @@ -34,7 +34,7 @@ Feature: consult/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4142 + gas_used: 4143 answer: has_more: false variables: ["Who"] @@ -90,7 +90,7 @@ Feature: consult/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4141 + gas_used: 4142 answer: has_more: false variables: ["X"] diff --git a/x/logic/keeper/features/current_output_1.feature b/x/logic/keeper/features/current_output_1.feature index bbf9ffa3..605862c1 100644 --- a/x/logic/keeper/features/current_output_1.feature +++ b/x/logic/keeper/features/current_output_1.feature @@ -28,7 +28,7 @@ Feature: current_output/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4240 + gas_used: 4241 answer: has_more: false variables: @@ -66,7 +66,7 @@ Feature: current_output/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4274 + gas_used: 4276 answer: has_more: false variables: @@ -104,7 +104,7 @@ Feature: current_output/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4240 + gas_used: 4242 answer: has_more: false variables: @@ -145,7 +145,7 @@ Feature: current_output/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4254 + gas_used: 4263 answer: has_more: false variables: @@ -176,7 +176,7 @@ Feature: current_output/1 Then the answer we get is: """ yaml height: 42 - gas_used: 4525 + gas_used: 4721 answer: has_more: false variables: diff --git a/x/logic/keeper/features/open_4.feature b/x/logic/keeper/features/open_4.feature index 6438b31e..6dec237b 100644 --- a/x/logic/keeper/features/open_4.feature +++ b/x/logic/keeper/features/open_4.feature @@ -43,7 +43,7 @@ Feature: open/4 Then the answer we get is: """ yaml height: 42 - gas_used: 4146 + gas_used: 4153 answer: has_more: false variables: ["URI"] @@ -86,7 +86,7 @@ Feature: open/4 Then the answer we get is: """ yaml height: 42 - gas_used: 4142 + gas_used: 4144 answer: has_more: false variables: ["Chars"] @@ -127,7 +127,7 @@ Feature: open/4 Then the answer we get is: """ yaml height: 42 - gas_used: 4142 + gas_used: 4144 answer: has_more: false variables: ["Chars"] diff --git a/x/logic/keeper/interpreter.go b/x/logic/keeper/interpreter.go index 3a06e276..991bae7a 100644 --- a/x/logic/keeper/interpreter.go +++ b/x/logic/keeper/interpreter.go @@ -9,6 +9,7 @@ import ( "github.com/ichiban/prolog" "github.com/ichiban/prolog/engine" "github.com/samber/lo" + orderedmap "github.com/wk8/go-ordered-map/v2" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" @@ -82,38 +83,9 @@ func (k Keeper) newInterpreter(ctx context.Context, params types.Params) (*prolo sdkctx := sdk.UnwrapSDKContext(ctx) interpreterParams := params.GetInterpreter() - gasPolicy := params.GetGasPolicy() - limits := params.GetLimits() - gasMeter := meter.WithWeightedMeter(sdkctx.GasMeter(), nonNilNorZeroOrDefaultUint64(gasPolicy.WeightingFactor, defaultWeightFactor)) - whitelistPredicates := util.NonZeroOrDefault(interpreterParams.PredicatesFilter.Whitelist, interpreter.RegistryNames) blacklistPredicates := interpreterParams.PredicatesFilter.Blacklist - hook := func(predicate string) func(env *engine.Env) (err error) { - return func(env *engine.Env) (err error) { - if !util.WhitelistBlacklistMatches(whitelistPredicates, blacklistPredicates, prolog2.PredicateMatches)(predicate) { - return engine.PermissionError( - prolog2.AtomOperationExecute, prolog2.AtomPermissionForbiddenPredicate, engine.NewAtom(predicate), env) - } - cost := lookupCost(predicate, defaultPredicateCost, gasPolicy.PredicateCosts) - - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case storetypes.ErrorOutOfGas: - err = errorsmod.Wrapf( - types.LimitExceeded, "out of gas: %s <%s> (%d/%d)", - types.ModuleName, rType.Descriptor, sdkctx.GasMeter().GasConsumed(), sdkctx.GasMeter().Limit()) - default: - panic(r) - } - } - }() - gasMeter.ConsumeGas(cost, predicate) - return err - } - } - whitelistUrls := lo.Map( util.NonZeroOrDefault(interpreterParams.VirtualFilesFilter.Whitelist, []string{}), util.Indexed(util.ParseURLMust)) @@ -122,6 +94,7 @@ func (k Keeper) newInterpreter(ctx context.Context, params types.Params) (*prolo util.Indexed(util.ParseURLMust)) var userOutputBuffer writerStringer + limits := params.GetLimits() if limits.MaxUserOutputSize != nil && limits.MaxUserOutputSize.GT(sdkmath.ZeroUint()) { userOutputBuffer = util.NewBoundedBufferMust(int(limits.MaxUserOutputSize.Uint64())) } else { @@ -129,7 +102,11 @@ func (k Keeper) newInterpreter(ctx context.Context, params types.Params) (*prolo } options := []interpreter.Option{ - interpreter.WithPredicates(ctx, interpreter.RegistryNames, hook), + interpreter.WithHooks( + whitelistBlacklistHookFn(whitelistPredicates, blacklistPredicates), + gasMeterHookFn(sdkctx, params.GetGasPolicy()), + ), + interpreter.WithPredicates(ctx, interpreter.RegistryNames), interpreter.WithBootstrap(ctx, util.NonZeroOrDefault(interpreterParams.GetBootstrap(), bootstrap.Bootstrap())), interpreter.WithFS(filtered.NewFS(k.fsProvider(ctx), whitelistUrls, blacklistUrls)), interpreter.WithUserOutputWriter(userOutputBuffer), @@ -141,7 +118,86 @@ func (k Keeper) newInterpreter(ctx context.Context, params types.Params) (*prolo return i, userOutputBuffer, err } +// whitelistBlacklistHookFn returns a hook function that checks if the given predicate is allowed to be executed. +// The predicate is allowed if it is in the whitelist or not in the blacklist. +func whitelistBlacklistHookFn(whitelist, blacklist []string) engine.HookFunc { + allowed := lo.Reduce( + lo.Filter(interpreter.RegistryNames, + util.Indexed(util.WhitelistBlacklistMatches(whitelist, blacklist, prolog2.PredicateMatches))), + func(agg *orderedmap.OrderedMap[string, struct{}], item string, _ int) *orderedmap.OrderedMap[string, struct{}] { + agg.Set(item, struct{}{}) + return agg + }, + orderedmap.New[string, struct{}](orderedmap.WithCapacity[string, struct{}](len(interpreter.RegistryNames)))) + + return func(opcode engine.Opcode, operand engine.Term, env *engine.Env) error { + if opcode != engine.OpCall { + return nil + } + + predicateStringer, ok := operand.(fmt.Stringer) + if !ok { + return engine.SyntaxError(operand, env) + } + + predicate := predicateStringer.String() + + if interpreter.IsRegistered(predicate) { + if _, found := allowed.Get(predicate); !found { + return engine.PermissionError( + prolog2.AtomOperationExecute, + prolog2.AtomPermissionForbiddenPredicate, + engine.NewAtom(predicate), + env, + ) + } + } + return nil + } +} + +// gasMeterHookFn returns a hook function that consumes gas based on the cost of the executed predicate. +func gasMeterHookFn(ctx context.Context, gasPolicy types.GasPolicy) engine.HookFunc { + sdkctx := sdk.UnwrapSDKContext(ctx) + gasMeter := meter.WithWeightedMeter(sdkctx.GasMeter(), nonNilNorZeroOrDefaultUint64(gasPolicy.WeightingFactor, defaultWeightFactor)) + + return func(opcode engine.Opcode, operand engine.Term, env *engine.Env) (err error) { + if opcode != engine.OpCall { + return nil + } + + operandStringer, ok := operand.(fmt.Stringer) + if !ok { + return engine.SyntaxError(operand, env) + } + + predicate := operandStringer.String() + + cost := lookupCost(predicate, defaultPredicateCost, gasPolicy.PredicateCosts) + + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case storetypes.ErrorOutOfGas: + err = errorsmod.Wrapf( + types.LimitExceeded, "out of gas: %s <%s> (%d/%d)", + types.ModuleName, rType.Descriptor, sdkctx.GasMeter().GasConsumed(), sdkctx.GasMeter().Limit()) + default: + panic(r) + } + } + }() + gasMeter.ConsumeGas(cost, predicate) + + return nil + } +} + func lookupCost(predicate string, defaultCost uint64, costs []types.PredicateCost) uint64 { + if !interpreter.IsRegistered(predicate) { + return defaultCost + } + for _, c := range costs { if prolog2.PredicateMatches(predicate)(c.Predicate) { return nonNilNorZeroOrDefaultUint64(c.Cost, defaultCost) From 897523b3fc4db13246d083de1862faf34b6c0826 Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 18:03:21 +0200 Subject: [PATCH 2/7] test(logic): improve test coverage for predicate blacklisting --- x/logic/keeper/grpc_query_ask_test.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/x/logic/keeper/grpc_query_ask_test.go b/x/logic/keeper/grpc_query_ask_test.go index 83c28385..defef5fa 100644 --- a/x/logic/keeper/grpc_query_ask_test.go +++ b/x/logic/keeper/grpc_query_ask_test.go @@ -239,7 +239,29 @@ func TestGRPCAsk(t *testing.T) { expectedAnswer: &types.Answer{ HasMore: false, Variables: []string{"X"}, - Results: []types.Result{{Error: "error(permission_error(execute,forbidden_predicate,block_height/1),block_height/1)"}}, + Results: []types.Result{{Error: "error(permission_error(execute,forbidden_predicate,block_height/1),root)"}}, + }, + }, + { + program: "contains_forbidden_predicate(X) :- block_height(X).", + query: "contains_forbidden_predicate(X).", + predicateBlacklist: []string{"block_height/1"}, + expectedAnswer: &types.Answer{ + HasMore: false, + Variables: []string{"X"}, + Results: []types.Result{{Error: "error(permission_error(execute,forbidden_predicate,block_height/1),contains_forbidden_predicate/1)"}}, + }, + }, + { + program: "cannot_be_blacklisted(X) :- X = 42.", + query: "cannot_be_blacklisted(X).", + predicateBlacklist: []string{"cannot_be_blacklisted/1"}, + expectedAnswer: &types.Answer{ + HasMore: false, + Variables: []string{"X"}, + Results: []types.Result{{Substitutions: []types.Substitution{{ + Variable: "X", Expression: "42", + }}}}, }, }, { From 1259b6ce2f2d8b848d312b004ed65a9b5a55a020 Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 18:05:53 +0200 Subject: [PATCH 3/7] chore(deps): bump github.com/axone-protocol/prolog to latest --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4fcee595..0f0f0863 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ replace ( github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.0 // Use cutom fork of prolog interpreter - github.com/ichiban/prolog => github.com/axone-protocol/prolog v1.0.0 + github.com/ichiban/prolog => github.com/axone-protocol/prolog v1.0.1-0.20240924120526-53584b2b5c0b // replace broken goleveldb github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 7978d719..b0090b28 100644 --- a/go.sum +++ b/go.sum @@ -292,8 +292,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ= github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/axone-protocol/prolog v1.0.0 h1:CASA1QrPOWhYox8YUStML33rekoA/7Gnp/ldDPZqCTA= -github.com/axone-protocol/prolog v1.0.0/go.mod h1:lbZPekEi6qr5WX29GgEmhZlTxUkeWeiJ8cZZRq8qjAE= +github.com/axone-protocol/prolog v1.0.1-0.20240924120526-53584b2b5c0b h1:s4U2NJBSdjZHRVOQuS2USITMFZO+Y3DuQBZagP527Q4= +github.com/axone-protocol/prolog v1.0.1-0.20240924120526-53584b2b5c0b/go.mod h1:lbZPekEi6qr5WX29GgEmhZlTxUkeWeiJ8cZZRq8qjAE= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= From 58f5ea759afcaa7ba089012e302f1dcbb05eaadf Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 18:21:47 +0200 Subject: [PATCH 4/7] test(logic): add test for Predicate Recursion of Death scenario --- x/logic/keeper/grpc_query_ask_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x/logic/keeper/grpc_query_ask_test.go b/x/logic/keeper/grpc_query_ask_test.go index defef5fa..881ad795 100644 --- a/x/logic/keeper/grpc_query_ask_test.go +++ b/x/logic/keeper/grpc_query_ask_test.go @@ -165,6 +165,12 @@ func TestGRPCAsk(t *testing.T) { }, expectedError: "out of gas: logic (11167/3000): limit exceeded", }, + { + program: "recursionOfDeath :- recursionOfDeath.", + query: "recursionOfDeath.", + maxGas: 3000, + expectedError: "out of gas: logic (3001/3000): limit exceeded", + }, { query: "length(List, 100000).", maxVariables: 1000, From eddda67652ce7c959f87246baec019949f9b2c60 Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 18:22:16 +0200 Subject: [PATCH 5/7] test(logic): add test for Backtrack of Death scenario --- x/logic/keeper/grpc_query_ask_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/logic/keeper/grpc_query_ask_test.go b/x/logic/keeper/grpc_query_ask_test.go index 881ad795..d989b8e1 100644 --- a/x/logic/keeper/grpc_query_ask_test.go +++ b/x/logic/keeper/grpc_query_ask_test.go @@ -28,6 +28,7 @@ import ( "github.com/axone-protocol/axoned/v10/x/logic/types" ) +//nolint:lll func TestGRPCAsk(t *testing.T) { emptySolution := types.Result{} Convey("Given a test cases", t, func() { @@ -171,6 +172,12 @@ func TestGRPCAsk(t *testing.T) { maxGas: 3000, expectedError: "out of gas: logic (3001/3000): limit exceeded", }, + { + program: "backtrackOfDeath :- repeat, fail.", + query: "backtrackOfDeath.", + maxGas: 3014, + expectedError: "out of gas: logic (3015/3014): limit exceeded", + }, { query: "length(List, 100000).", maxVariables: 1000, From 63f68dfa16a5939ff0d82cbaed7e8f97f20c2f24 Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 21:13:52 +0200 Subject: [PATCH 6/7] docs(README): refresh make help section --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 591d49fd..38d65f0e 100644 --- a/README.md +++ b/README.md @@ -120,18 +120,17 @@ Targets: chain-init Initialize the blockchain with default settings. chain-start Start the blockchain with existing configuration (see chain-init) chain-stop Stop the blockchain - chain-upgrade Test the chain upgrade from the given FROM_VERSION to the given TO_VERSION + chain-upgrade Test the chain upgrade from the given FROM_VERSION to the given TO_VERSION. You can pass also the proposal json file on PROPOSAL var Clean: clean Remove all the files from the target folder Proto: proto Generate all resources for proto files (go, doc, etc.) - proto-format Format Protobuf files - proto-build Build all Protobuf files proto-gen Generate all the code from the Protobuf files Documentation: doc Generate all the documentation doc-proto Generate the documentation from the Protobuf files doc-command Generate markdown documentation for the command + doc-predicate Generate markdown documentation for all the predicates (module logic) Mock: mock Generate all the mocks (for tests) Release: From a1aa708a176080ece0945df9a73178932ecb0c82 Mon Sep 17 00:00:00 2001 From: ccamel Date: Tue, 24 Sep 2024 21:16:38 +0200 Subject: [PATCH 7/7] docs(predicates): update scenarii to reflect real gas consumption --- docs/predicate/bech32_address_2.md | 2 +- docs/predicate/consult_1.md | 4 ++-- docs/predicate/current_output_1.md | 8 ++++---- docs/predicate/open_4.md | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/predicate/bech32_address_2.md b/docs/predicate/bech32_address_2.md index a213ad73..c5777e7a 100644 --- a/docs/predicate/bech32_address_2.md +++ b/docs/predicate/bech32_address_2.md @@ -162,7 +162,7 @@ axone_addr('axone1p8u47en82gmzfm259y6z93r9qe63l25d858vqu'). ``` yaml height: 42 -gas_used: 4140 +gas_used: 4141 answer: has_more: false results: diff --git a/docs/predicate/consult_1.md b/docs/predicate/consult_1.md index 030e5753..1b77df7d 100644 --- a/docs/predicate/consult_1.md +++ b/docs/predicate/consult_1.md @@ -65,7 +65,7 @@ hello(Who). ``` yaml height: 42 -gas_used: 4142 +gas_used: 4143 answer: has_more: false variables: ["Who"] @@ -129,7 +129,7 @@ response: | ``` yaml height: 42 -gas_used: 4141 +gas_used: 4142 answer: has_more: false variables: ["X"] diff --git a/docs/predicate/current_output_1.md b/docs/predicate/current_output_1.md index 2ba280f0..58c40444 100644 --- a/docs/predicate/current_output_1.md +++ b/docs/predicate/current_output_1.md @@ -61,7 +61,7 @@ write_char_to_user_output(x). ``` yaml height: 42 -gas_used: 4240 +gas_used: 4241 answer: has_more: false variables: @@ -108,7 +108,7 @@ log_message('Hello world!'). ``` yaml height: 42 -gas_used: 4274 +gas_used: 4276 answer: has_more: false variables: @@ -155,7 +155,7 @@ log_message('Hello world!'). ``` yaml height: 42 -gas_used: 4240 +gas_used: 4242 answer: has_more: false variables: @@ -205,7 +205,7 @@ log_message("Hello 🧙!"). ``` yaml height: 42 -gas_used: 4254 +gas_used: 4263 answer: has_more: false variables: diff --git a/docs/predicate/open_4.md b/docs/predicate/open_4.md index a9fd1262..8a3e2c5f 100644 --- a/docs/predicate/open_4.md +++ b/docs/predicate/open_4.md @@ -100,7 +100,7 @@ open(URI, read, _, []). ``` yaml height: 42 -gas_used: 4146 +gas_used: 4153 answer: has_more: false variables: ["URI"] @@ -152,7 +152,7 @@ read_resource('cosmwasm:storage:axone15ekvz3qdter33mdnk98v8whv5qdr53yusksnfgc08x ``` yaml height: 42 -gas_used: 4142 +gas_used: 4144 answer: has_more: false variables: ["Chars"] @@ -202,7 +202,7 @@ read_resource('cosmwasm:storage:axone15ekvz3qdter33mdnk98v8whv5qdr53yusksnfgc08x ``` yaml height: 42 -gas_used: 4142 +gas_used: 4144 answer: has_more: false variables: ["Chars"]