diff --git a/pkg/proto/transactions_test.go b/pkg/proto/transactions_test.go index f600f0305..1126b8971 100644 --- a/pkg/proto/transactions_test.go +++ b/pkg/proto/transactions_test.go @@ -5983,7 +5983,7 @@ func TestInvokeScriptWithProofsValidations(t *testing.T) { repeat := func(arg StringArgument, n int) Arguments { r := make([]Argument, n) for i := 0; i < n; i++ { - r = append(r, arg) + r = append(r, &arg) } return r } @@ -6000,13 +6000,13 @@ func TestInvokeScriptWithProofsValidations(t *testing.T) { err string version byte }{ - {ScriptPayments{}, "foo", Arguments{IntegerArgument{Value: 1234567890}}, 0, "fee should be positive", 1}, - {ScriptPayments{{12345, *a1}}, "foo", Arguments{StringArgument{Value: "some value should be ok"}}, math.MaxInt64 + 1, "fee is too big", 1}, + {ScriptPayments{}, "foo", Arguments{&IntegerArgument{Value: 1234567890}}, 0, "fee should be positive", 1}, + {ScriptPayments{{12345, *a1}}, "foo", Arguments{&StringArgument{Value: "some value should be ok"}}, math.MaxInt64 + 1, "fee is too big", 1}, {ScriptPayments{{12345, *a1}}, strings.Repeat("foo", 100), Arguments{}, 13245, "function name is too big", 1}, {ScriptPayments{{12345, *a1}}, "foo", repeat(StringArgument{Value: "some value should be ok"}, 100), 13245, "too many arguments", 1}, - {ScriptPayments{{0, *a1}}, "foo", Arguments{StringArgument{Value: "some value should be ok"}}, 1234, "at least one payment has a non-positive amount", 1}, - {ScriptPayments{{math.MaxInt64 + 123, *a1}}, "foo", Arguments{StringArgument{Value: "some value should be ok"}}, 12345, "at least one payment has a too big amount", 1}, - {ScriptPayments{}, "foo", Arguments{IntegerArgument{Value: 1234567890}}, 1, "unexpected version 128 for InvokeScriptWithProofs", 128}, + {ScriptPayments{{0, *a1}}, "foo", Arguments{&StringArgument{Value: "some value should be ok"}}, 1234, "at least one payment has a non-positive amount", 1}, + {ScriptPayments{{math.MaxInt64 + 123, *a1}}, "foo", Arguments{&StringArgument{Value: "some value should be ok"}}, 12345, "at least one payment has a too big amount", 1}, + {ScriptPayments{}, "foo", Arguments{&IntegerArgument{Value: 1234567890}}, 1, "unexpected version 128 for InvokeScriptWithProofs", 128}, //TODO: add test on arguments evaluation } for _, tc := range tests { diff --git a/pkg/proto/types.go b/pkg/proto/types.go index 89208956b..a18806443 100644 --- a/pkg/proto/types.go +++ b/pkg/proto/types.go @@ -3189,16 +3189,16 @@ func NewIntegerArgument(i int64) *IntegerArgument { } //GetValueType returns the value type of the entry. -func (a IntegerArgument) GetValueType() ArgumentValueType { +func (a *IntegerArgument) GetValueType() ArgumentValueType { return ArgumentInteger } -func (a IntegerArgument) BinarySize() int { +func (a *IntegerArgument) BinarySize() int { return integerArgumentLen } //MarshalBinary marshals the integer argument in its bytes representation. -func (a IntegerArgument) MarshalBinary() ([]byte, error) { +func (a *IntegerArgument) MarshalBinary() ([]byte, error) { buf := make([]byte, a.BinarySize()) pos := 0 buf[pos] = byte(ArgumentInteger) @@ -3208,7 +3208,7 @@ func (a IntegerArgument) MarshalBinary() ([]byte, error) { } //Serialize the integer argument in its bytes representation. -func (a IntegerArgument) Serialize(s *serializer.Serializer) error { +func (a *IntegerArgument) Serialize(s *serializer.Serializer) error { err := s.Byte(byte(ArgumentInteger)) if err != nil { return err @@ -3255,16 +3255,16 @@ type BooleanArgument struct { } //GetValueType returns the data type (Boolean) of the argument. -func (a BooleanArgument) GetValueType() ArgumentValueType { +func (a *BooleanArgument) GetValueType() ArgumentValueType { return ArgumentBoolean } -func (a BooleanArgument) BinarySize() int { +func (a *BooleanArgument) BinarySize() int { return booleanArgumentLen } //MarshalBinary writes a byte representation of the boolean data entry. -func (a BooleanArgument) MarshalBinary() ([]byte, error) { +func (a *BooleanArgument) MarshalBinary() ([]byte, error) { buf := make([]byte, a.BinarySize()) if a.Value { buf[0] = byte(ArgumentValueTrue) @@ -3275,7 +3275,7 @@ func (a BooleanArgument) MarshalBinary() ([]byte, error) { } //Serialize argument to its byte representation. -func (a BooleanArgument) Serialize(s *serializer.Serializer) error { +func (a *BooleanArgument) Serialize(s *serializer.Serializer) error { buf := byte(0) if a.Value { buf = byte(ArgumentValueTrue) @@ -3328,16 +3328,16 @@ type BinaryArgument struct { } //GetValueType returns the type of value (Binary) stored in an argument. -func (a BinaryArgument) GetValueType() ArgumentValueType { +func (a *BinaryArgument) GetValueType() ArgumentValueType { return ArgumentBinary } -func (a BinaryArgument) BinarySize() int { +func (a *BinaryArgument) BinarySize() int { return binaryArgumentMinLen + len(a.Value) } //MarshalBinary writes an argument to its byte representation. -func (a BinaryArgument) MarshalBinary() ([]byte, error) { +func (a *BinaryArgument) MarshalBinary() ([]byte, error) { buf := make([]byte, a.BinarySize()) pos := 0 buf[pos] = byte(ArgumentBinary) @@ -3347,7 +3347,7 @@ func (a BinaryArgument) MarshalBinary() ([]byte, error) { } //Serialize argument to its byte representation. -func (a BinaryArgument) Serialize(s *serializer.Serializer) error { +func (a *BinaryArgument) Serialize(s *serializer.Serializer) error { err := s.Byte(byte(ArgumentBinary)) if err != nil { return err @@ -3402,16 +3402,16 @@ func NewStringArgument(s string) *StringArgument { } //GetValueType returns the type of value of the argument. -func (a StringArgument) GetValueType() ArgumentValueType { +func (a *StringArgument) GetValueType() ArgumentValueType { return ArgumentString } -func (a StringArgument) BinarySize() int { +func (a *StringArgument) BinarySize() int { return stringArgumentMinLen + len(a.Value) } //MarshalBinary converts the argument to its byte representation. -func (a StringArgument) MarshalBinary() ([]byte, error) { +func (a *StringArgument) MarshalBinary() ([]byte, error) { buf := make([]byte, a.BinarySize()) pos := 0 buf[pos] = byte(ArgumentString) @@ -3421,7 +3421,7 @@ func (a StringArgument) MarshalBinary() ([]byte, error) { } //Serialize argument to its byte representation. -func (a StringArgument) Serialize(s *serializer.Serializer) error { +func (a *StringArgument) Serialize(s *serializer.Serializer) error { err := s.Byte(byte(ArgumentString)) if err != nil { return err @@ -3471,16 +3471,16 @@ type ListArgument struct { } //GetValueType returns the type of value of the argument. -func (a ListArgument) GetValueType() ArgumentValueType { +func (a *ListArgument) GetValueType() ArgumentValueType { return ArgumentList } -func (a ListArgument) BinarySize() int { +func (a *ListArgument) BinarySize() int { return 1 + a.Items.BinarySize() } //MarshalBinary converts the argument to its byte representation. -func (a ListArgument) MarshalBinary() ([]byte, error) { +func (a *ListArgument) MarshalBinary() ([]byte, error) { buf := make([]byte, a.BinarySize()) pos := 0 buf[pos] = byte(ArgumentList) @@ -3494,7 +3494,7 @@ func (a ListArgument) MarshalBinary() ([]byte, error) { } //Serialize argument to its byte representation. -func (a ListArgument) Serialize(s *serializer.Serializer) error { +func (a *ListArgument) Serialize(s *serializer.Serializer) error { err := s.Byte(byte(ArgumentList)) if err != nil { return err diff --git a/pkg/ride/environment.go b/pkg/ride/environment.go index d38b8455a..a47f661f5 100644 --- a/pkg/ride/environment.go +++ b/pkg/ride/environment.go @@ -13,6 +13,13 @@ var ( errDeletedEntry = errors.New("entry has been deleted") ) +var ( + libV2CheckMessageLength = func(int) bool { return true } + libV3CheckMessageLength = func(l int) bool { + return l <= maxMessageLength + } +) + type WrappedState struct { diff diffState cle rideAddress @@ -943,6 +950,7 @@ type EvaluationEnvironment struct { ver ast.LibraryVersion validatePaymentsAfter uint64 isBlockV5Activated bool + isRiveV5Activated bool isRiveV6Activated bool isProtobufTransaction bool mds int @@ -957,7 +965,7 @@ func NewEnvironment(scheme proto.Scheme, state types.SmartState, internalPayment sch: scheme, st: state, h: rideInt(height), - check: func(int) bool { return true }, // By default, for versions below 2 there was no check, always ok. + check: libV2CheckMessageLength, // By default, for versions below 2 there was no check, always ok. takeStr: func(s string, n int) rideString { panic("function 'takeStr' was not initialized") }, validatePaymentsAfter: internalPaymentsValidationHeight, }, nil @@ -968,6 +976,7 @@ func NewEnvironmentWithWrappedState( payments proto.ScriptPayments, sender proto.WavesAddress, isBlockV5Activated bool, + isRideV5Activated bool, isRideV6Activated bool, isProtobufTransaction bool, rootScriptLibVersion ast.LibraryVersion, @@ -1040,6 +1049,7 @@ func NewEnvironmentWithWrappedState( validatePaymentsAfter: env.validatePaymentsAfter, mds: env.mds, isBlockV5Activated: isBlockV5Activated, + isRiveV5Activated: isRideV5Activated, isRiveV6Activated: isRideV6Activated, isProtobufTransaction: isProtobufTransaction, }, nil @@ -1049,6 +1059,10 @@ func (e *EvaluationEnvironment) rideV6Activated() bool { return e.isRiveV6Activated } +func (e *EvaluationEnvironment) rideV5Activated() bool { + return e.isRiveV5Activated +} + func (e *EvaluationEnvironment) blockV5Activated() bool { return e.isBlockV5Activated } @@ -1063,9 +1077,7 @@ func (e *EvaluationEnvironment) ChooseTakeString(isRideV5 bool) { func (e *EvaluationEnvironment) ChooseSizeCheck(v ast.LibraryVersion) { e.ver = v if v > ast.LibV2 { - e.check = func(l int) bool { - return l <= maxMessageLength - } + e.check = libV3CheckMessageLength } } diff --git a/pkg/ride/errors.go b/pkg/ride/errors.go index fe37e879e..a8cd31fbf 100644 --- a/pkg/ride/errors.go +++ b/pkg/ride/errors.go @@ -21,17 +21,13 @@ type evaluationError struct { originalError error callStack []string spentComplexity int + complexities []int } func (e evaluationError) Error() string { return e.originalError.Error() } -func (e evaluationError) AddComplexity(complexity int) error { - e.spentComplexity += complexity - return e -} - func (e EvaluationError) New(msg string) error { return evaluationError{errorType: e, originalError: errors.New(msg)} } @@ -62,6 +58,13 @@ func EvaluationErrorCallStack(err error) []string { return nil } +func EvaluationErrorReverseComplexitiesList(err error) []int { + if ee, ok := err.(evaluationError); ok { + return ee.complexities + } + return nil +} + func EvaluationErrorSpentComplexity(err error) int { if ee, ok := err.(evaluationError); ok { return ee.spentComplexity @@ -71,12 +74,27 @@ func EvaluationErrorSpentComplexity(err error) int { func EvaluationErrorPush(err error, format string, args ...interface{}) error { if ee, ok := err.(evaluationError); ok { - ee.callStack = append([]string{fmt.Sprintf(format, args...)}, ee.callStack...) + elem := fmt.Sprintf(format, args...) + if cap(ee.callStack) > len(ee.callStack) { // reusing the same memory area + ee.callStack = append(ee.callStack[:1], ee.callStack...) + ee.callStack[0] = elem + } else { // allocating memory + ee.callStack = append([]string{elem}, ee.callStack...) + } return ee } return errors.Wrapf(err, format, args...) } +func EvaluationErrorPushComplexity(err error, complexity int) error { + if ee, ok := err.(evaluationError); ok { + ee.complexities = append(ee.complexities, complexity) + ee.spentComplexity += complexity + return ee + } + return err +} + func EvaluationErrorAddComplexity(err error, complexity int) error { if ee, ok := err.(evaluationError); ok { ee.spentComplexity += complexity diff --git a/pkg/ride/evaluation_complexity_test.go b/pkg/ride/evaluation_complexity_test.go new file mode 100644 index 000000000..eb8b29bc8 --- /dev/null +++ b/pkg/ride/evaluation_complexity_test.go @@ -0,0 +1,524 @@ +package ride + +import ( + "encoding/base64" + "strconv" + "strings" + "testing" + + "github.com/mr-tron/base58" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/ride/ast" + "github.com/wavesplatform/gowaves/pkg/types" +) + +type testStage struct { + env *mockRideEnvironment + state *WrappedState + inv rideObject + this proto.WavesAddress + libVersion ast.LibraryVersion + rideV5Activated bool + rideV6Activated bool + trees map[proto.WavesAddress]*ast.Tree + publicKeys map[proto.WavesAddress]crypto.PublicKey +} + +func makeTestStage(inv, tx rideObject, this proto.WavesAddress, rideV5, rideV6 bool, libVersion ast.LibraryVersion, + trees map[proto.WavesAddress]*ast.Tree, publicKeys map[proto.WavesAddress]crypto.PublicKey) *testStage { + const ( + dAppBalance = 10_00000000 - 1000000 // 9.99 WAVES + accountBalance = 10_00000000 // 10 WAVES + ) + r := &testStage{ + inv: inv, + this: this, + libVersion: libVersion, + rideV5Activated: rideV5, + rideV6Activated: rideV6, + trees: trees, + publicKeys: publicKeys, + } + r.env = &mockRideEnvironment{ + schemeFunc: func() byte { + return proto.TestNetScheme + }, + thisFunc: func() rideType { + return rideAddress(r.this) + }, + transactionFunc: func() rideObject { + return tx + }, + invocationFunc: func() rideObject { + return r.inv + }, + checkMessageLengthFunc: v3check, + setInvocationFunc: func(inv rideObject) { + r.inv = inv + }, + validateInternalPaymentsFunc: trueFn, + maxDataEntriesSizeFunc: func() int { + return proto.MaxDataEntriesScriptActionsSizeInBytesV2 + }, + blockV5ActivatedFunc: func() bool { + return true + }, + rideV5ActivatedFunc: func() bool { + return r.rideV5Activated + }, + rideV6ActivatedFunc: func() bool { + return r.rideV6Activated + }, + isProtobufTxFunc: isProtobufTx, + libVersionFunc: func() ast.LibraryVersion { + return r.libVersion + }, + } + state := &MockSmartState{ + NewestScriptByAccountFunc: func(recipient proto.Recipient) (*ast.Tree, error) { + if tree, ok := r.trees[*recipient.Address]; ok { + return tree, nil + } + return nil, errors.Errorf("unexpected recipient '%s'", recipient.String()) + }, + NewestScriptPKByAddrFunc: func(addr proto.WavesAddress) (crypto.PublicKey, error) { + if pk, ok := r.publicKeys[addr]; ok { + return pk, nil + } + return crypto.PublicKey{}, errors.Errorf("unexpected address %s", addr.String()) + }, + NewestRecipientToAddressFunc: func(recipient proto.Recipient) (*proto.WavesAddress, error) { + if _, ok := r.trees[*recipient.Address]; ok { + return recipient.Address, nil + } + if _, ok := r.publicKeys[*recipient.Address]; ok { + return recipient.Address, nil + } + return nil, errors.Errorf("unexpected recipient '%s'", recipient.String()) + }, + NewestWavesBalanceFunc: func(account proto.Recipient) (uint64, error) { + if _, ok := r.trees[*account.Address]; ok { + return dAppBalance, nil + } + if _, ok := r.publicKeys[*account.Address]; ok { + return accountBalance, nil + } + return 0, errors.Errorf("unxepected account '%s'", account.String()) + }, + NewestFullWavesBalanceFunc: func(account proto.Recipient) (*proto.FullWavesBalance, error) { + if _, ok := r.trees[*account.Address]; ok { + return &proto.FullWavesBalance{ + Regular: dAppBalance, + Generating: dAppBalance, + Available: dAppBalance, + Effective: dAppBalance, + LeaseIn: 0, + LeaseOut: 0, + }, nil + } + if _, ok := r.publicKeys[*account.Address]; ok { + return &proto.FullWavesBalance{ + Regular: accountBalance, + Generating: accountBalance, + Available: accountBalance, + Effective: accountBalance, + LeaseIn: 0, + LeaseOut: 0, + }, nil + } + return &proto.FullWavesBalance{}, nil + }, + } + r.state = &WrappedState{ + diff: newDiffState(state), + cle: r.env.this().(rideAddress), + scheme: r.env.scheme(), + rootScriptLibVersion: r.libVersion, + rootActionsCountValidator: proto.NewScriptActionsCountValidator(), + } + r.env.stateFunc = func() types.SmartState { + return r.state + } + r.env.setNewDAppAddressFunc = func(address proto.WavesAddress) { + r.this = address + r.state.cle = rideAddress(address) // We have to update wrapped state's `cle` + } + return r +} + +// parseArguments converts string of comma separated string with prefix encoded values to the slice of proto.Argument. +// To encode proto.StringArgument use string "s'xxx'", "xxx" is the argument value. +// To encode proto.IntegerArgument use string "i'12345'", 12345 is the value of the argument. +// To encode proto.BooleanArgument use string "b'true'" or "b'false'". +// To encode proto.BinaryArgument use string "b58'1111'" or "b64'dGVzdA=='. +// Eg, "s'TEST', i'123456', b'true', "b64'dGVzdA=='" +func parseArguments(t *testing.T, arguments string) proto.Arguments { + if arguments == "" { + return proto.Arguments{} + } + args := strings.Split(arguments, ",") + r := make(proto.Arguments, len(args)) + for i, a := range args { + sp := strings.Split(strings.TrimSpace(a), "'") + switch sp[0] { + case "s": + r[i] = &proto.StringArgument{Value: sp[1]} + case "i": + v, err := strconv.ParseInt(sp[1], 10, 64) + require.NoError(t, err) + r[i] = &proto.IntegerArgument{Value: v} + case "b": + v, err := strconv.ParseBool(sp[1]) + require.NoError(t, err) + r[i] = &proto.BooleanArgument{Value: v} + case "b64": + v, err := base64.StdEncoding.DecodeString(sp[1]) + require.NoError(t, err) + r[i] = &proto.BinaryArgument{Value: v} + case "b58": + v, err := base58.Decode(sp[1]) + require.NoError(t, err) + r[i] = &proto.BinaryArgument{Value: v} + default: + t.Fatalf("unsupported argument prefix '%s'", sp[0]) + } + } + return r +} + +func makeInvokeTransactionTestObjects(t *testing.T, senderPK crypto.PublicKey, dAppAddress proto.WavesAddress, functionName, arguments string) (rideObject, rideObject) { + call := proto.FunctionCall{ + Default: false, + Name: functionName, + Arguments: parseArguments(t, arguments), + } + tx := &proto.InvokeScriptWithProofs{ + Type: proto.InvokeScriptTransaction, + Version: 1, + ID: makeRandomTxID(t), + Proofs: proto.NewProofs(), + ChainID: proto.TestNetScheme, + SenderPK: senderPK, + ScriptRecipient: proto.NewRecipientFromAddress(dAppAddress), + FunctionCall: call, + Payments: proto.ScriptPayments{}, + FeeAsset: proto.OptionalAsset{}, + Fee: 500000, + Timestamp: 1624967106278, + } + invObj, err := invocationToObject(5, proto.TestNetScheme, tx) + require.NoError(t, err) + txObj, err := transactionToObject(proto.TestNetScheme, tx) + require.NoError(t, err) + return invObj, txObj +} + +func TestComplexitiesV5V6(t *testing.T) { + _, dApp1PK, dApp1 := makeAddressAndPK(t, "DAPP1") // 3MzDtgL5yw73C2xVLnLJCrT5gCL4357a4sz + _, dApp2PK, dApp2 := makeAddressAndPK(t, "DAPP2") // 3N7Te7NXtGVoQqFqktwrFhQWAkc6J8vfPQ1 + _, dApp3PK, dApp3 := makeAddressAndPK(t, "DAPP3") // 3N186hYM5PFwGdkVUsLJaBvpPEECrSj5CJh + _, dApp4PK, dApp4 := makeAddressAndPK(t, "DAPP4") // 3Mtfbvy5nEGNR2ZNAWJUHauEfFsBysAr1S6 + _, senderPK, sender := makeAddressAndPK(t, "SENDER") // 3N8CkZAyS4XcDoJTJoKNuNk2xmNKmQj7myW + + /* On dApp1 + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + let dapp2 = Address(base58'3N7Te7NXtGVoQqFqktwrFhQWAkc6J8vfPQ1') + + let message = base58'emsY' + let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + + let complex = sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify_64Kb(message, sig, pub) + let complexCase4 = sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify_64Kb(message, sig, pub) + let complexCase56 = sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify(message, sig, pub) && sigVerify_32Kb(message, sig, pub) + + @Callable(i) + func case1() = { + strict inv = invoke(dapp2, "case1", [complex], []) + [] + } + + @Callable(i) + func case2() = { + strict inv = invoke(dapp2, "case2", [complex], []) + [] + } + + @Callable(i) + func case3() = { + strict inv = invoke(dapp2, "case3", [complex], []) + [] + } + + @Callable(i) + func case4() = { + strict inv = invoke(dapp2, "case4", [complex && complexCase4], []) + [] + } + + @Callable(i) + func case5() = { + strict inv = invoke(dapp2, "case5", [complexCase56], []) + [] + } + + @Callable(i) + func case6() = { + strict inv = invoke(dapp2, "case6", [complexCase56], []) + [] + } + */ + code1 := "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" + _, tree1 := parseBase64Script(t, code1) + + assert.NotNil(t, tree1) + /* On dApp2 + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + let dapp3 = Address(base58'3N186hYM5PFwGdkVUsLJaBvpPEECrSj5CJh') + + let message = base58'emsY' + let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + + let complexCase1 = sigVerify_8Kb(message, sig, pub) + let complexCase2 = sigVerify_32Kb(message, sig, pub) && sigVerify_16Kb(message, sig, pub) + let complexCase34 = sigVerify(message, sig, pub) && sigVerify(message, sig, pub) + let complexCase5 = sigVerify_8Kb(message, sig, pub) && sigVerify_8Kb(message, sig, pub) + let complexCase6 = sigVerify_64Kb(message, sig, pub) && sigVerify_32Kb(message, sig, pub) + + @Callable(i) + func case1(bool:Boolean) = { + strict inv = invoke(dapp3, "case1", [complexCase1], []) + [] + } + + @Callable(i) + func case2(bool:Boolean) = { + strict inv = invoke(dapp3, "case2", [complexCase2], []) + [] + } + + @Callable(i) + func case3(bool:Boolean) = { + strict inv = invoke(dapp3, "case3", [complexCase34], []) + [] + } + + @Callable(i) + func case4(bool:Boolean) = { + if (complexCase34) then { + [ScriptTransfer(dapp3, 99900000000, unit)] + } else [] + } + + @Callable(i) + func case5(bool:Boolean) = { + strict inv = invoke(dapp3, "case5", [complexCase5], []) + [] + } + + @Callable(i) + func case6(bool:Boolean) = { + strict inv = invoke(dapp3, "case6", [complexCase6], []) + [] + } + */ + code2 := "AAIFAAAAAAAAACAIAhIDCgEEEgMKAQQSAwoBBBIDCgEEEgMKAQQSAwoBBAAAAAkAAAAABWRhcHAzCQEAAAAHQWRkcmVzcwAAAAEBAAAAGgFUeu8lmsRjc2kucGmTq6Am5fkIjxQl3OMuAAAAAAdtZXNzYWdlAQAAAANwdWsAAAAAA3B1YgEAAAAg+WDTl1J9TPwoZK08IrX5nepU6IE6+Sop9sM4jqz/Ti4AAAAAA3NpZwEAAABAw1mP1rioR7NALQUl9VfqzYKgyTigqanddUm2A1+z5zJdAr+g4iKaVKvD4agOP4Nsv84I8Ht1zgDkT9N9jRU0gQAAAAAMY29tcGxleENhc2UxCQAJxAAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViAAAAAAxjb21wbGV4Q2FzZTIDCQAJxgAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViCQAJxQAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViBwAAAAANY29tcGxleENhc2UzNAMJAAH0AAAAAwUAAAAHbWVzc2FnZQUAAAADc2lnBQAAAANwdWIJAAH0AAAAAwUAAAAHbWVzc2FnZQUAAAADc2lnBQAAAANwdWIHAAAAAAxjb21wbGV4Q2FzZTUDCQAJxAAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViCQAJxAAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViBwAAAAAMY29tcGxleENhc2U2AwkACccAAAADBQAAAAdtZXNzYWdlBQAAAANzaWcFAAAAA3B1YgkACcYAAAADBQAAAAdtZXNzYWdlBQAAAANzaWcFAAAAA3B1YgcAAAAGAAAAAWkBAAAABWNhc2UxAAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDMCAAAABWNhc2UxCQAETAAAAAIFAAAADGNvbXBsZXhDYXNlMQUAAAADbmlsBQAAAANuaWwDCQAAAAAAAAIFAAAAA2ludgUAAAADaW52BQAAAANuaWwJAAACAAAAAQIAAAAkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAAAAAWkBAAAABWNhc2UyAAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDMCAAAABWNhc2UyCQAETAAAAAIFAAAADGNvbXBsZXhDYXNlMgUAAAADbmlsBQAAAANuaWwDCQAAAAAAAAIFAAAAA2ludgUAAAADaW52BQAAAANuaWwJAAACAAAAAQIAAAAkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAAAAAWkBAAAABWNhc2UzAAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDMCAAAABWNhc2UzCQAETAAAAAIFAAAADWNvbXBsZXhDYXNlMzQFAAAAA25pbAUAAAADbmlsAwkAAAAAAAACBQAAAANpbnYFAAAAA2ludgUAAAADbmlsCQAAAgAAAAECAAAAJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgAAAAFpAQAAAAVjYXNlNAAAAAEAAAAEYm9vbAMFAAAADWNvbXBsZXhDYXNlMzQJAARMAAAAAgkBAAAADlNjcmlwdFRyYW5zZmVyAAAAAwUAAAAFZGFwcDMAAAAAF0KBBwAFAAAABHVuaXQFAAAAA25pbAUAAAADbmlsAAAAAWkBAAAABWNhc2U1AAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDMCAAAABWNhc2U1CQAETAAAAAIFAAAADGNvbXBsZXhDYXNlNQUAAAADbmlsBQAAAANuaWwDCQAAAAAAAAIFAAAAA2ludgUAAAADaW52BQAAAANuaWwJAAACAAAAAQIAAAAkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAAAAAWkBAAAABWNhc2U2AAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDMCAAAABWNhc2U2CQAETAAAAAIFAAAADGNvbXBsZXhDYXNlNgUAAAADbmlsBQAAAANuaWwDCQAAAAAAAAIFAAAAA2ludgUAAAADaW52BQAAAANuaWwJAAACAAAAAQIAAAAkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAAAAAKUeqZ4=" + _, tree2 := parseBase64Script(t, code2) + + /* On dApp3 + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + let message = base58'emsY' + let pub = base58'HnU9jfhpMcQNaG5yQ46eR43RnkWKGxerw2zVrbpnbGof' + let sig = base58'4uXfw7162zaopAkTNa7eo6YK2mJsTiHGJL3dCtRRH63z1nrdoHBHyhbvrfZovkxf2jKsi2vPsaP2XykfZmUiwPeg' + + let dapp4 = Address(base58'3Mtfbvy5nEGNR2ZNAWJUHauEfFsBysAr1S6') + + let complexCase56 = sigVerify_16Kb(message, sig, pub) && sigVerify_8Kb(message, sig, pub) + + @Callable(i) + func case1(bool:Boolean) = { + if (sigVerify(message, sig, pub)) then + [ScriptTransfer(i.caller, 99900000000, unit)] + else [] + } + + @Callable(i) + func case2(bool:Boolean) = { + [ScriptTransfer(i.caller, 99900000000, unit)] + } + + @Callable(i) + func case3(bool:Boolean) = { + [ScriptTransfer(i.caller, 99900000000, unit)] + } + + @Callable(i) + func case5(bool:Boolean) = { + strict inv = invoke(dapp4, "case5", [complexCase56], []) + [] + } + + @Callable(i) + func case6(bool:Boolean) = { + strict inv = invoke(dapp4, "case6", [complexCase56], []) + [] + } + */ + code3 := "AAIFAAAAAAAAABsIAhIDCgEEEgMKAQQSAwoBBBIDCgEEEgMKAQQAAAAFAAAAAAdtZXNzYWdlAQAAAANwdWsAAAAAA3B1YgEAAAAg+WDTl1J9TPwoZK08IrX5nepU6IE6+Sop9sM4jqz/Ti4AAAAAA3NpZwEAAABAw1mP1rioR7NALQUl9VfqzYKgyTigqanddUm2A1+z5zJdAr+g4iKaVKvD4agOP4Nsv84I8Ht1zgDkT9N9jRU0gQAAAAAFZGFwcDQJAQAAAAdBZGRyZXNzAAAAAQEAAAAaAVQ0G4/8L+t0U77mKK9peiJfObsVZZe0WycAAAAADWNvbXBsZXhDYXNlNTYDCQAJxQAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViCQAJxAAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViBwAAAAUAAAABaQEAAAAFY2FzZTEAAAABAAAABGJvb2wDCQAB9AAAAAMFAAAAB21lc3NhZ2UFAAAAA3NpZwUAAAADcHViCQAETAAAAAIJAQAAAA5TY3JpcHRUcmFuc2ZlcgAAAAMIBQAAAAFpAAAABmNhbGxlcgAAAAAXQoEHAAUAAAAEdW5pdAUAAAADbmlsBQAAAANuaWwAAAABaQEAAAAFY2FzZTIAAAABAAAABGJvb2wJAARMAAAAAgkBAAAADlNjcmlwdFRyYW5zZmVyAAAAAwgFAAAAAWkAAAAGY2FsbGVyAAAAABdCgQcABQAAAAR1bml0BQAAAANuaWwAAAABaQEAAAAFY2FzZTMAAAABAAAABGJvb2wJAARMAAAAAgkBAAAADlNjcmlwdFRyYW5zZmVyAAAAAwgFAAAAAWkAAAAGY2FsbGVyAAAAABdCgQcABQAAAAR1bml0BQAAAANuaWwAAAABaQEAAAAFY2FzZTUAAAABAAAABGJvb2wEAAAAA2ludgkAA/wAAAAEBQAAAAVkYXBwNAIAAAAFY2FzZTUJAARMAAAAAgUAAAANY29tcGxleENhc2U1NgUAAAADbmlsBQAAAANuaWwDCQAAAAAAAAIFAAAAA2ludgUAAAADaW52BQAAAANuaWwJAAACAAAAAQIAAAAkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAAAAAWkBAAAABWNhc2U2AAAAAQAAAARib29sBAAAAANpbnYJAAP8AAAABAUAAAAFZGFwcDQCAAAABWNhc2U2CQAETAAAAAIFAAAADWNvbXBsZXhDYXNlNTYFAAAAA25pbAUAAAADbmlsAwkAAAAAAAACBQAAAANpbnYFAAAAA2ludgUAAAADbmlsCQAAAgAAAAECAAAAJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgAAAAAmhwaJ" + _, tree3 := parseBase64Script(t, code3) + + /* On dApp4 + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + @Callable(i) + func case5(bool:Boolean) = { + [ScriptTransfer(i.caller, 99900000000, unit)] + } + + @Callable(i) + func case6(bool:Boolean) = { + [ScriptTransfer(i.caller, 99900000000, unit)] + } + */ + code4 := "AAIFAAAAAAAAAAwIAhIDCgEEEgMKAQQAAAAAAAAAAgAAAAFpAQAAAAVjYXNlNQAAAAEAAAAEYm9vbAkABEwAAAACCQEAAAAOU2NyaXB0VHJhbnNmZXIAAAADCAUAAAABaQAAAAZjYWxsZXIAAAAAF0KBBwAFAAAABHVuaXQFAAAAA25pbAAAAAFpAQAAAAVjYXNlNgAAAAEAAAAEYm9vbAkABEwAAAACCQEAAAAOU2NyaXB0VHJhbnNmZXIAAAADCAUAAAABaQAAAAZjYWxsZXIAAAAAF0KBBwAFAAAABHVuaXQFAAAAA25pbAAAAABv44L5" + _, tree4 := parseBase64Script(t, code4) + + invObj, txObj := makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case1", "") + tst := makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err := CallFunction(tst.env, tree1, "case1", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 797+130, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case1", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1105, EvaluationErrorSpentComplexity(err), err.Error()) + + invObj, txObj = makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case2", "") + tst = makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err = CallFunction(tst.env, tree1, "case2", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 797+214, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case2", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 985, EvaluationErrorSpentComplexity(err), err.Error()) + + invObj, txObj = makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case3", "") + tst = makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err = CallFunction(tst.env, tree1, "case3", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 797+487, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case3", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1258, EvaluationErrorSpentComplexity(err), err.Error()) + + invObj, txObj = makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case4", "") + tst = makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err = CallFunction(tst.env, tree1, "case4", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1516, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case4", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1884, EvaluationErrorSpentComplexity(err), err.Error()) + + invObj, txObj = makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case5", "") + tst = makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err = CallFunction(tst.env, tree1, "case5", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 765+181+191, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case5", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1101, EvaluationErrorSpentComplexity(err), err.Error()) + + invObj, txObj = makeInvokeTransactionTestObjects(t, senderPK, dApp1, "case6", "") + tst = makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree1, dApp2: tree2, dApp3: tree3, dApp4: tree4}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, dApp2: dApp2PK, dApp3: dApp3PK, dApp4: dApp4PK, sender: senderPK}) + _, err = CallFunction(tst.env, tree1, "case6", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 765+259+191, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree1, "case6", parseArguments(t, "")) + require.Error(t, err) + assert.Equal(t, 1179, EvaluationErrorSpentComplexity(err), err.Error()) +} + +func TestSelfInvokeComplexities(t *testing.T) { + _, dApp1PK, dApp1 := makeAddressAndPK(t, "DAPP1") // 3MzDtgL5yw73C2xVLnLJCrT5gCL4357a4sz + _, senderPK, sender := makeAddressAndPK(t, "SENDER") // 3N8CkZAyS4XcDoJTJoKNuNk2xmNKmQj7myW + + /* On dApp1 + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + @Callable(i) + func call( r: Int ) = { + if( r == 0 ) then [ ScriptTransfer(Address(base58'3N8CkZAyS4XcDoJTJoKNuNk2xmNKmQj7myW'), 1000000000, unit ) ] else + let f = fraction( fraction( r, 1, 1 ), 1, 1 ) + strict g = invoke( this, "call", [ f - 1 ], [] ) + [] + } + */ + code := "AAIFAAAAAAAAAAcIAhIDCgEBAAAAAAAAAAEAAAABaQEAAAAEY2FsbAAAAAEAAAABcgMJAAAAAAAAAgUAAAABcgAAAAAAAAAAAAkABEwAAAACCQEAAAAOU2NyaXB0VHJhbnNmZXIAAAADCQEAAAAHQWRkcmVzcwAAAAEBAAAAGgFUyJlKpWZVh64i0Ak7F/0vFuHDEV0ZUdLtAAAAAAA7msoABQAAAAR1bml0BQAAAANuaWwEAAAAAWYJAABrAAAAAwkAAGsAAAADBQAAAAFyAAAAAAAAAAABAAAAAAAAAAABAAAAAAAAAAABAAAAAAAAAAABBAAAAAFnCQAD/AAAAAQFAAAABHRoaXMCAAAABGNhbGwJAARMAAAAAgkAAGUAAAACBQAAAAFmAAAAAAAAAAABBQAAAANuaWwFAAAAA25pbAMJAAAAAAAAAgUAAAABZwUAAAABZwUAAAADbmlsCQAAAgAAAAECAAAAJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgAAAAAyP+y9" + _, tree := parseBase64Script(t, code) + + invObj, txObj := makeInvokeTransactionTestObjects(t, senderPK, dApp1, "call", "i'9'") + tst := makeTestStage(invObj, txObj, dApp1, true, false, ast.LibV5, + map[proto.WavesAddress]*ast.Tree{dApp1: tree}, + map[proto.WavesAddress]crypto.PublicKey{dApp1: dApp1PK, sender: senderPK}) + _, err := CallFunction(tst.env, tree, "call", parseArguments(t, "i'9'")) + require.Error(t, err) + assert.Equal(t, 904, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree, "call", parseArguments(t, "i'9'")) + require.Error(t, err) + assert.Equal(t, 958, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = false + _, err = CallFunction(tst.env, tree, "call", parseArguments(t, "i'10'")) + require.Error(t, err) + assert.Equal(t, 1017, EvaluationErrorSpentComplexity(err), err.Error()) + + tst.rideV6Activated = true + _, err = CallFunction(tst.env, tree, "call", parseArguments(t, "i'10'")) + require.Error(t, err) + assert.Equal(t, 1064, EvaluationErrorSpentComplexity(err), err.Error()) + +} diff --git a/pkg/ride/functions_invocations.go b/pkg/ride/functions_invocations.go index 3cdac834f..3101f3735 100644 --- a/pkg/ride/functions_invocations.go +++ b/pkg/ride/functions_invocations.go @@ -24,7 +24,7 @@ func invokeFunctionFromDApp(env environment, recipient proto.Recipient, fnName r res, err := e.evaluate() if err != nil { // Evaluation failed we have to add spent execution complexity to an error - return nil, EvaluationErrorAddComplexity(err, e.complexity()) + return nil, EvaluationErrorPushComplexity(err, e.complexity()) } return res, nil } diff --git a/pkg/ride/functions_proto.go b/pkg/ride/functions_proto.go index 7ada9cbce..ea6f3219d 100644 --- a/pkg/ride/functions_proto.go +++ b/pkg/ride/functions_proto.go @@ -111,7 +111,7 @@ func (i *reentrantInvocation) blocklist() bool { return false } -func performInvoke(invocation invocation, env environment, args ...rideType) (rideType, error) { +func performInvoke(invocation invocation, env environment, args ...rideType) (out rideType, err error) { ws, ok := env.state().(*WrappedState) if !ok { return nil, EvaluationFailure.Errorf("%s: wrong state", invocation.name()) @@ -214,6 +214,12 @@ func performInvoke(invocation invocation, env environment, args ...rideType) (ri if err != nil { return nil, EvaluationErrorPush(err, "%s at '%s' function '%s' with arguments %v", invocation.name(), recipient.Address.String(), fn, arguments) } + defer func() { + if err != nil { + // Evaluation checks failed, but we have to add spent execution complexity to an error + err = EvaluationErrorPushComplexity(err, res.Complexity()) + } + }() err = ws.smartAppendActions(res.ScriptActions(), env, &localActionsCountValidator) if err != nil { @@ -223,9 +229,7 @@ func performInvoke(invocation invocation, env environment, args ...rideType) (ri return nil, err } - if env.validateInternalPayments() && !env.rideV6Activated() { - err = ws.validateBalances(env.rideV6Activated()) - } else if env.rideV6Activated() { + if env.validateInternalPayments() && !env.rideV6Activated() || env.rideV6Activated() { err = ws.validateBalances(env.rideV6Activated()) } if err != nil { @@ -238,6 +242,7 @@ func performInvoke(invocation invocation, env environment, args ...rideType) (ri env.setNewDAppAddress(proto.WavesAddress(callerAddress)) env.setInvocation(oldInvocationParam) + // after this line no error should be returned because it can lead to complexity doubling (see defer above) ws.totalComplexity += res.Complexity() if res.userResult() == nil { diff --git a/pkg/ride/functions_proto_test.go b/pkg/ride/functions_proto_test.go index b58ea1591..c91c94f30 100644 --- a/pkg/ride/functions_proto_test.go +++ b/pkg/ride/functions_proto_test.go @@ -19,16 +19,17 @@ import ( ) var ( - v2check = func(int) bool { - return true - } - v3check = func(size int) bool { - return size <= maxMessageLength - } + trueFn = func() bool { return true } + falseFn = func() bool { return false } +) + +var ( + v2check = libV2CheckMessageLength + v3check = libV3CheckMessageLength v5takeString = takeRideString - noRideV6 = func() bool { - return false - } + yesRideV5 = trueFn + yesRideV6 = trueFn + noRideV6 = falseFn ) func TestAddressFromString(t *testing.T) { diff --git a/pkg/ride/functions_test.go b/pkg/ride/functions_test.go index a0d1db3ec..717a08bf0 100644 --- a/pkg/ride/functions_test.go +++ b/pkg/ride/functions_test.go @@ -2,6 +2,7 @@ package ride import ( "math/rand" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -51,3 +52,19 @@ func BenchmarkCheckFunctionMap(b *testing.B) { assert.True(b, ok) } } + +func TestInvokeCallComplexityV5Constant(t *testing.T) { + const ( + invokeFunctionID = 1020 + reentrantInvokeFunctionID = 1021 + ) + catalogues := &[...]map[string]int{ + CatalogueV5, + EvaluationCatalogueV5EvaluatorV1, + EvaluationCatalogueV5EvaluatorV2, + } + for _, catalogue := range catalogues { + assert.Equal(t, catalogue[strconv.Itoa(invokeFunctionID)], invokeCallComplexityV5) + assert.Equal(t, catalogue[strconv.Itoa(reentrantInvokeFunctionID)], invokeCallComplexityV5) + } +} diff --git a/pkg/ride/runtime.go b/pkg/ride/runtime.go index 9f371071b..43cef77f4 100644 --- a/pkg/ride/runtime.go +++ b/pkg/ride/runtime.go @@ -715,6 +715,7 @@ type environment interface { libVersion() ast.LibraryVersion validateInternalPayments() bool blockV5Activated() bool + rideV5Activated() bool rideV6Activated() bool internalPaymentsValidationHeight() uint64 maxDataEntriesSize() int diff --git a/pkg/ride/runtime_moq_test.go b/pkg/ride/runtime_moq_test.go index e321354e4..bed29e804 100644 --- a/pkg/ride/runtime_moq_test.go +++ b/pkg/ride/runtime_moq_test.go @@ -47,6 +47,9 @@ var _ environment = &mockRideEnvironment{} // maxDataEntriesSizeFunc: func() int { // panic("mock out the maxDataEntriesSize method") // }, +// rideV5ActivatedFunc: func() bool { +// panic("mock out the rideV5Activated method") +// }, // rideV6ActivatedFunc: func() bool { // panic("mock out the rideV6Activated method") // }, @@ -114,6 +117,9 @@ type mockRideEnvironment struct { // maxDataEntriesSizeFunc mocks the maxDataEntriesSize method. maxDataEntriesSizeFunc func() int + // rideV5ActivatedFunc mocks the rideV5Activated method. + rideV5ActivatedFunc func() bool + // rideV6ActivatedFunc mocks the rideV6Activated method. rideV6ActivatedFunc func() bool @@ -178,6 +184,9 @@ type mockRideEnvironment struct { // maxDataEntriesSize holds details about calls to the maxDataEntriesSize method. maxDataEntriesSize []struct { } + // rideV5Activated holds details about calls to the rideV5Activated method. + rideV5Activated []struct { + } // rideV6Activated holds details about calls to the rideV6Activated method. rideV6Activated []struct { } @@ -229,6 +238,7 @@ type mockRideEnvironment struct { lockisProtobufTx sync.RWMutex locklibVersion sync.RWMutex lockmaxDataEntriesSize sync.RWMutex + lockrideV5Activated sync.RWMutex lockrideV6Activated sync.RWMutex lockscheme sync.RWMutex locksetInvocation sync.RWMutex @@ -481,6 +491,32 @@ func (mock *mockRideEnvironment) maxDataEntriesSizeCalls() []struct { return calls } +// rideV5Activated calls rideV5ActivatedFunc. +func (mock *mockRideEnvironment) rideV5Activated() bool { + if mock.rideV5ActivatedFunc == nil { + panic("mockRideEnvironment.rideV5ActivatedFunc: method is nil but environment.rideV5Activated was just called") + } + callInfo := struct { + }{} + mock.lockrideV5Activated.Lock() + mock.calls.rideV5Activated = append(mock.calls.rideV5Activated, callInfo) + mock.lockrideV5Activated.Unlock() + return mock.rideV5ActivatedFunc() +} + +// rideV5ActivatedCalls gets all the calls that were made to rideV5Activated. +// Check the length with: +// len(mockedenvironment.rideV5ActivatedCalls()) +func (mock *mockRideEnvironment) rideV5ActivatedCalls() []struct { +} { + var calls []struct { + } + mock.lockrideV5Activated.RLock() + calls = mock.calls.rideV5Activated + mock.lockrideV5Activated.RUnlock() + return calls +} + // rideV6Activated calls rideV6ActivatedFunc. func (mock *mockRideEnvironment) rideV6Activated() bool { if mock.rideV6ActivatedFunc == nil { diff --git a/pkg/ride/tree_evaluation.go b/pkg/ride/tree_evaluation.go index 34f0ef27d..9f1624530 100644 --- a/pkg/ride/tree_evaluation.go +++ b/pkg/ride/tree_evaluation.go @@ -6,6 +6,9 @@ import ( "github.com/wavesplatform/gowaves/pkg/types" ) +// invokeCallComplexityV5 is invoke() or reentrantInvoke() functions cost for RideV5 +const invokeCallComplexityV5 = 75 + func CallVerifier(env environment, tree *ast.Tree) (Result, error) { e, err := treeVerifierEvaluator(env, tree) if err != nil { @@ -32,29 +35,20 @@ func CallFunction(env environment, tree *ast.Tree, name string, args proto.Argum if err != nil { // Evaluation failed we have to return a DAppResult that contains spent execution complexity // Produced actions are not stored for failed transactions, no need to return them here - et := GetEvaluationErrorType(err) - if et == Undefined { - return nil, EvaluationErrorAddComplexity( - et.Wrap(err, "unhandled error"), - // Error was not handled in wrapped state properly, - // so we need to add both complexity from current evaluation and from internal invokes - e.complexity()+wrappedStateComplexity(env.state()), - ) - } - return nil, EvaluationErrorAddComplexity(err, e.complexity()+wrappedStateComplexity(env.state())) + return nil, handleComplexityInCaseOfEvaluationError(err, e, env) } dAppResult, ok := rideResult.(DAppResult) if !ok { // Unexpected result type return nil, EvaluationErrorAddComplexity( EvaluationFailure.Errorf("invalid result of call function '%s'", name), - // New error, both complexities should be added + // New error, both complexities should be added (also see comment in complexityInCaseOfEvaluationError) e.complexity()+wrappedStateComplexity(env.state()), ) } if tree.LibVersion < ast.LibV5 { // Shortcut because no wrapped state before version 5 return rideResult, nil } - maxChainInvokeComplexity, err := maxChainInvokeComplexityByVersion(ast.LibraryVersion(tree.LibVersion)) + maxChainInvokeComplexity, err := maxChainInvokeComplexityByVersion(tree.LibVersion) if err != nil { return nil, EvaluationFailure.Errorf("failed to get max chain invoke complexity: %v", err) } @@ -88,3 +82,44 @@ func wrappedStateActions(state types.SmartState) []proto.ScriptAction { } return ws.act } + +func evaluationErrorSetComplexity(err error, complexity int) error { + if ee, ok := err.(evaluationError); ok { + ee.spentComplexity = complexity + return ee + } + return err +} + +func handleComplexityInCaseOfEvaluationError(err error, e *treeEvaluator, env environment) error { + // Error was not handled in wrapped state properly, + // so we need to add complexities from current evaluation, from internal invokes and from internal failed invoke + totalComplexity := e.complexity() + wrappedStateComplexity(env.state()) + EvaluationErrorSpentComplexity(err) + switch et := GetEvaluationErrorType(err); et { + case Undefined: + return evaluationErrorSetComplexity(et.Wrap(err, "unhandled error"), totalComplexity) + case InternalInvocationError: //, RuntimeError: // TODO: ask about RuntimeError + // reproduce scala's node buggy behaviour + if ws, ok := env.state().(*WrappedState); ok && env.rideV5Activated() && !env.rideV6Activated() { + // if invoke script tx depth level is 2 or less ==> complexity should be set to 0 + // invCount() is calls count of invoke() or reentrantInvoke() functions ==> txDepthLevel = 1 + invCount() + if txDepthLevel := 1 + ws.invCount(); txDepthLevel <= 2 { + totalComplexity = 0 + } else { + // if depth level is 3 or greater, then we should sub last two invoke complexities plus + // cost of the last invoke() or reentrantInvoke() function call + reverseComplexitiesList := EvaluationErrorReverseComplexitiesList(err) + if l := len(reverseComplexitiesList); l < 2 { + return evaluationErrorSetComplexity(Undefined.Wrapf(err, + "reverseComplexitiesStack size=%d must be greater than 2", l, + ), totalComplexity) + } + lastTwoInvokesCost := reverseComplexitiesList[0] + reverseComplexitiesList[1] + invokeCallComplexityV5 + totalComplexity -= lastTwoInvokesCost + } + } + return evaluationErrorSetComplexity(err, totalComplexity) + default: + return evaluationErrorSetComplexity(err, totalComplexity) + } +} diff --git a/pkg/ride/tree_evaluation_test.go b/pkg/ride/tree_evaluation_test.go index f666fa127..8324bf586 100644 --- a/pkg/ride/tree_evaluation_test.go +++ b/pkg/ride/tree_evaluation_test.go @@ -28,9 +28,7 @@ var ( }, rideV6ActivatedFunc: noRideV6, } - isProtobufTx = func() bool { - return true - } + isProtobufTx = trueFn ) func TestSimpleScriptEvaluation(t *testing.T) { @@ -176,10 +174,8 @@ func TestFunctionsEvaluation(t *testing.T) { } return obj }, - validateInternalPaymentsFunc: func() bool { - return false - }, - rideV6ActivatedFunc: noRideV6, + validateInternalPaymentsFunc: falseFn, + rideV6ActivatedFunc: noRideV6, } envWithDataTX := &mockRideEnvironment{ transactionFunc: func() rideObject { @@ -1115,13 +1111,10 @@ var envDappFromDapp = &mockRideEnvironment{ timestampFunc: func() uint64 { return 1564703444249 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - validateInternalPaymentsFunc: func() bool { - return true - }, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: noRideV6, + validateInternalPaymentsFunc: trueFn, internalPaymentsValidationHeightFunc: func() uint64 { return 0 }, @@ -1140,6 +1133,7 @@ func tearDownDappFromDapp() { addressCallable = proto.WavesAddress{} addrPK = crypto.PublicKey{} addressCallablePK = crypto.PublicKey{} + envDappFromDapp.rideV5ActivatedFunc = trueFn envDappFromDapp.rideV6ActivatedFunc = noRideV6 thisAddress = proto.WavesAddress{} @@ -1507,9 +1501,7 @@ func TestInvokeBalanceValidationV6(t *testing.T) { thisAddress = addr env := envDappFromDapp - env.rideV6ActivatedFunc = func() bool { - return true - } + env.rideV6ActivatedFunc = trueFn _, tree := parseBase64Script(t, firstScript) @@ -1665,9 +1657,7 @@ func TestInvokeFailedBalanceValidationV6(t *testing.T) { thisAddress = addr env := envDappFromDapp - env.rideV6ActivatedFunc = func() bool { - return true - } + env.rideV6ActivatedFunc = trueFn _, tree := parseBase64Script(t, firstScript) @@ -5590,10 +5580,8 @@ func TestMathDApp(t *testing.T) { require.NoError(t, err) return obj }, - validateInternalPaymentsFunc: func() bool { - return false - }, - rideV6ActivatedFunc: noRideV6, + validateInternalPaymentsFunc: falseFn, + rideV6ActivatedFunc: noRideV6, } code := "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" @@ -7061,13 +7049,9 @@ func TestOriginCaller(t *testing.T) { maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - validateInternalPaymentsFunc: func() bool { - return true - }, - blockV5ActivatedFunc: func() bool { - return true - }, - isProtobufTxFunc: isProtobufTx, + validateInternalPaymentsFunc: trueFn, + blockV5ActivatedFunc: trueFn, + isProtobufTxFunc: isProtobufTx, } mockState := &MockSmartState{ @@ -7232,17 +7216,14 @@ func TestInternalPaymentsValidationFailure(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: noRideV6, setNewDAppAddressFunc: func(address proto.WavesAddress) { testDAppAddress = address testState.cle = rideAddress(address) }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -7321,9 +7302,7 @@ func TestInternalPaymentsValidationFailure(t *testing.T) { require.Error(t, err) // Turning off internal payments validation - env.validateInternalPaymentsFunc = func() bool { - return false - } + env.validateInternalPaymentsFunc = falseFn testInv, err = invocationToObject(5, proto.MainNetScheme, tx) require.NoError(t, err) testDAppAddress = dApp1 @@ -7414,17 +7393,13 @@ func TestAliasesInInvokes(t *testing.T) { invocationFunc: func() rideObject { return testInv }, - blockV5ActivatedFunc: func() bool { - return true - }, + blockV5ActivatedFunc: trueFn, rideV6ActivatedFunc: noRideV6, checkMessageLengthFunc: v3check, setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -7655,13 +7630,9 @@ func TestIssueAndTransferInInvoke(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - validateInternalPaymentsFunc: func() bool { - return true - }, + blockV5ActivatedFunc: trueFn, + rideV6ActivatedFunc: noRideV6, + validateInternalPaymentsFunc: trueFn, txIDFunc: func() rideType { return rideBytes(tx.ID.Bytes()) }, @@ -7850,22 +7821,17 @@ func TestTransferUnavailableFundsInInvoke(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, txIDFunc: func() rideType { return rideBytes(tx.ID.Bytes()) }, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: func() bool { - return true - }, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, + isProtobufTxFunc: isProtobufTx, } mockState := &MockSmartState{ @@ -8012,10 +7978,9 @@ func TestBurnAndFailOnTransferInInvokeAfterRideV6(t *testing.T) { thisFunc: func() rideType { return rideAddress(testDAppAddress) }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: noRideV6, transactionFunc: func() rideObject { obj, err := transactionToObject(proto.TestNetScheme, tx) require.NoError(t, err) @@ -8028,9 +7993,7 @@ func TestBurnAndFailOnTransferInInvokeAfterRideV6(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -8222,17 +8185,13 @@ func TestReissueInInvoke(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV6ActivatedFunc: noRideV6, + isProtobufTxFunc: isProtobufTx, } mockState := &MockSmartState{ @@ -8432,11 +8391,10 @@ func TestNegativePayments(t *testing.T) { maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: noRideV6, + isProtobufTxFunc: isProtobufTx, } mockState := &MockSmartState{ @@ -8636,17 +8594,13 @@ func TestComplexityOverflow(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV6ActivatedFunc: noRideV6, + isProtobufTxFunc: isProtobufTx, libVersionFunc: func() ast.LibraryVersion { return ast.LibV5 }, @@ -8780,17 +8734,13 @@ func TestDateEntryPutAfterRemoval(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV6ActivatedFunc: noRideV6, + isProtobufTxFunc: isProtobufTx, libVersionFunc: func() ast.LibraryVersion { return ast.LibV5 }, @@ -8922,17 +8872,14 @@ func TestFailRejectMultiLevelInvokesBeforeRideV6(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: noRideV6, + isProtobufTxFunc: isProtobufTx, libVersionFunc: func() ast.LibraryVersion { return ast.LibV5 }, @@ -9097,22 +9044,17 @@ func TestInvokeFailForRideV4(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, txIDFunc: func() rideType { return rideBytes(tx.ID.Bytes()) }, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: func() bool { - return true - }, - isProtobufTxFunc: isProtobufTx, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, + isProtobufTxFunc: isProtobufTx, } mockState := &MockSmartState{ @@ -9281,17 +9223,14 @@ func TestInvokeActionsCountRestrictionsV6ToV5Positive(t *testing.T) { invocationFunc: func() rideObject { return testInv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, checkMessageLengthFunc: v3check, setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -9341,19 +9280,21 @@ func TestInvokeActionsCountRestrictionsV6ToV5Positive(t *testing.T) { return proto.WavesAddress{}, errors.Errorf("unexpected alias '%s'", alias.String()) } }, - NewestWavesBalanceFunc: func(account proto.Recipient) (uint64, error) { + NewestFullWavesBalanceFunc: func(account proto.Recipient) (*proto.FullWavesBalance, error) { + var available uint64 switch account.String() { case dApp1.String(): - return 0, nil + available = 0 case caller.String(): - return 0, nil + available = 0 case dApp2.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case callee.String(): - return 100_000_000_000, nil + available = 100_000_000_000 default: - return 0, errors.Errorf("unexpected account '%s'", account.String()) + return nil, errors.Errorf("unexpected account '%s'", account.String()) } + return &proto.FullWavesBalance{Available: available}, nil }, WavesBalanceProfileFunc: func(id proto.AddressID) (*types.WavesBalanceProfile, error) { switch id { @@ -9537,17 +9478,14 @@ func TestInvokeActionsCountRestrictionsV6ToV5NestedPositive(t *testing.T) { invocationFunc: func() rideObject { return testInv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, checkMessageLengthFunc: v3check, setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -9597,19 +9535,21 @@ func TestInvokeActionsCountRestrictionsV6ToV5NestedPositive(t *testing.T) { return proto.WavesAddress{}, errors.Errorf("unexpected alias '%s'", alias.String()) } }, - NewestWavesBalanceFunc: func(account proto.Recipient) (uint64, error) { + NewestFullWavesBalanceFunc: func(account proto.Recipient) (*proto.FullWavesBalance, error) { + var available uint64 switch account.String() { case dApp1.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case caller.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case dApp2.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case callee.String(): - return 100_000_000_000, nil + available = 100_000_000_000 default: - return 0, errors.Errorf("unexpected account '%s'", account.String()) + return nil, errors.Errorf("unexpected account '%s'", account.String()) } + return &proto.FullWavesBalance{Available: available}, nil }, WavesBalanceProfileFunc: func(id proto.AddressID) (*types.WavesBalanceProfile, error) { switch id { @@ -9754,17 +9694,14 @@ func TestInvokeActionsCountRestrictionsV6ToV5OverflowNegative(t *testing.T) { invocationFunc: func() rideObject { return testInv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, checkMessageLengthFunc: v3check, setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -9814,19 +9751,21 @@ func TestInvokeActionsCountRestrictionsV6ToV5OverflowNegative(t *testing.T) { return proto.WavesAddress{}, errors.Errorf("unexpected alias '%s'", alias.String()) } }, - NewestWavesBalanceFunc: func(account proto.Recipient) (uint64, error) { + NewestFullWavesBalanceFunc: func(account proto.Recipient) (*proto.FullWavesBalance, error) { + var available uint64 switch account.String() { case dApp1.String(): - return 0, nil + available = 0 case caller.String(): - return 0, nil + available = 0 case dApp2.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case callee.String(): - return 100_000_000_000, nil + available = 100_000_000_000 default: - return 0, errors.Errorf("unexpected account '%s'", account.String()) + return nil, errors.Errorf("unexpected account '%s'", account.String()) } + return &proto.FullWavesBalance{Available: available}, nil }, WavesBalanceProfileFunc: func(id proto.AddressID) (*types.WavesBalanceProfile, error) { switch id { @@ -9979,17 +9918,14 @@ func TestInvokeActionsCountRestrictionsV6ToV5PNegative(t *testing.T) { invocationFunc: func() rideObject { return testInv }, - blockV5ActivatedFunc: func() bool { - return true - }, - rideV6ActivatedFunc: noRideV6, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, + rideV6ActivatedFunc: yesRideV6, checkMessageLengthFunc: v3check, setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, @@ -10039,19 +9975,21 @@ func TestInvokeActionsCountRestrictionsV6ToV5PNegative(t *testing.T) { return proto.WavesAddress{}, errors.Errorf("unexpected alias '%s'", alias.String()) } }, - NewestWavesBalanceFunc: func(account proto.Recipient) (uint64, error) { + NewestFullWavesBalanceFunc: func(account proto.Recipient) (*proto.FullWavesBalance, error) { + var available uint64 switch account.String() { case dApp1.String(): - return 0, nil + available = 0 case caller.String(): - return 0, nil + available = 0 case dApp2.String(): - return 100_000_000_000, nil + available = 100_000_000_000 case callee.String(): - return 100_000_000_000, nil + available = 100_000_000_000 default: - return 0, errors.Errorf("unexpected account '%s'", account.String()) + return nil, errors.Errorf("unexpected account '%s'", account.String()) } + return &proto.FullWavesBalance{Available: available}, nil }, WavesBalanceProfileFunc: func(id proto.AddressID) (*types.WavesBalanceProfile, error) { switch id { @@ -10172,18 +10110,15 @@ func TestInvokeDappAttachedPaymentsLimitAfterV6(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, txIDFunc: func() rideType { return rideBytes(tx.ID.Bytes()) }, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, + blockV5ActivatedFunc: trueFn, + rideV5ActivatedFunc: yesRideV5, rideV6ActivatedFunc: func() bool { return rideV6Activated }, @@ -10337,15 +10272,11 @@ func TestInvokeDappFromDappWithZeroPayments(t *testing.T) { setInvocationFunc: func(inv rideObject) { testInv = inv }, - validateInternalPaymentsFunc: func() bool { - return true - }, + validateInternalPaymentsFunc: trueFn, maxDataEntriesSizeFunc: func() int { return proto.MaxDataEntriesScriptActionsSizeInBytesV2 }, - blockV5ActivatedFunc: func() bool { - return true - }, + blockV5ActivatedFunc: trueFn, rideV6ActivatedFunc: func() bool { return rideV6Activated }, diff --git a/pkg/state/appender.go b/pkg/state/appender.go index 9930686a1..1014ce511 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -275,8 +275,9 @@ func (a *txAppender) checkScriptsLimits(scriptsRuns uint64, blockID proto.BlockI } maxBlockComplexity := NewMaxScriptsComplexityInBlock().GetMaxScriptsComplexityInBlock(rideV5Activated) if a.sc.getTotalComplexity() > uint64(maxBlockComplexity) { - // TODO this is definitely an error, should return it - zap.S().Warnf("Complexity of scripts (%d) in block '%s' exceeds limit of %d", a.sc.getTotalComplexity(), blockID.String(), maxBlockComplexity) + return errors.Errorf("complexity of scripts (%d) in block '%s' exceeds limit of %d", + a.sc.getTotalComplexity(), blockID.String(), maxBlockComplexity, + ) } return nil } else if smartAccountsActivated { diff --git a/pkg/state/blockreadwriter_test.go b/pkg/state/blockreadwriter_test.go index 33499a540..773d084ac 100644 --- a/pkg/state/blockreadwriter_test.go +++ b/pkg/state/blockreadwriter_test.go @@ -13,6 +13,7 @@ import ( "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/proto" "github.com/wavesplatform/gowaves/pkg/util/common" + "go.uber.org/atomic" ) const ( @@ -283,19 +284,16 @@ func TestSimultaneousReadWrite(t *testing.T) { if err != nil { t.Fatalf("Can not read blocks from blockchain file: %v", err) } - var mtx sync.Mutex var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) - errCounter := 0 + var errCounter atomic.Int64 readTasks := make(chan *readTask, tasksChanBufferSize) wg.Add(1) go func() { defer wg.Done() err1 := writeBlocks(ctx, to.rw, blocks, readTasks, true, false) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Writer error: %v\n", err1) cancel() } @@ -306,16 +304,14 @@ func TestSimultaneousReadWrite(t *testing.T) { defer wg.Done() err1 := testReader(to.rw, readTasks) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Reader error: %v\n", err1) cancel() } }() } wg.Wait() - if errCounter != 0 { + if errCounter.Load() != 0 { t.Fatalf("Reader/writer error.") } } @@ -338,19 +334,16 @@ func TestReadNewest(t *testing.T) { if err != nil { t.Fatalf("Can not read blocks from blockchain file: %v", err) } - var mtx sync.Mutex var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) - errCounter := 0 + var errCounter atomic.Int64 readTasks := make(chan *readTask, tasksChanBufferSize) wg.Add(1) go func() { defer wg.Done() err1 := writeBlocks(ctx, to.rw, blocks, readTasks, false, false) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Writer error: %v\n", err1) cancel() } @@ -361,16 +354,14 @@ func TestReadNewest(t *testing.T) { defer wg.Done() err1 := testNewestReader(to.rw, readTasks) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Reader error: %v\n", err1) cancel() } }() } wg.Wait() - if errCounter != 0 { + if errCounter.Load() != 0 { t.Fatalf("Reader/writer error.") } } @@ -480,19 +471,16 @@ func TestProtobufReadWrite(t *testing.T) { prevId = protobufBlocks[i].BlockID() } - var mtx sync.Mutex var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) - errCounter := 0 + var errCounter atomic.Int64 readTasks := make(chan *readTask, tasksChanBufferSize) wg.Add(1) go func() { defer wg.Done() err1 := writeBlocks(ctx, to.rw, protobufBlocks, readTasks, true, true) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Writer error: %v\n", err1) cancel() } @@ -503,16 +491,14 @@ func TestProtobufReadWrite(t *testing.T) { defer wg.Done() err1 := testReader(to.rw, readTasks) if err1 != nil { - mtx.Lock() - errCounter++ - mtx.Unlock() + errCounter.Inc() fmt.Printf("Reader error: %v\n", err1) cancel() } }() } wg.Wait() - if errCounter != 0 { + if errCounter.Load() != 0 { t.Fatalf("Reader/writer error.") } } diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index 9705975e0..bf18e55fc 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -839,55 +839,7 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV // Call script function. r, err := ia.sc.invokeFunction(tree, tx, info, *scriptAddr) if err != nil { - // Script returned error, it's OK, but we have to decide is it failed or rejected transaction. - // After activation of RideV6 feature transactions are failed if they are not cheap regardless the error kind. - isCheap := int(ia.sc.recentTxComplexity) <= FailFreeInvokeComplexity - if info.rideV6Activated { - if !info.acceptFailed || isCheap { - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) - } - // Before RideV6 activation in the following cases the transaction is rejected: - // 1) Failing of transactions is not activated yet, reject everything - // 2) The error is ride.InternalInvocationError and correct fail/reject behaviour is activated - // 3) The spent complexity is less than limit - switch ride.GetEvaluationErrorType(err) { - case ride.UserError, ride.RuntimeError: - // Usual script error produced by user code or system functions. - // We reject transaction if spent complexity is less than limit. - if !info.acceptFailed || isCheap { // Reject transaction if no failed transactions or the transaction is cheap - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) - case ride.InternalInvocationError: - // Special script error produced by internal script invocation or application of results. - // Reject transaction after certain height - rejectOnInvocationError := info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight - if !info.acceptFailed || rejectOnInvocationError || isCheap { - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) - case ride.Undefined, ride.EvaluationFailure: // Unhandled or evaluator error - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) - default: - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) - } + return ia.handleInvokeScriptInvocationError(err, txID, failedChanges, info) } var scriptRuns uint64 = 0 // After activation of RideV5 (16) feature we don't take extra fee for execution of smart asset scripts. @@ -949,6 +901,52 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV return ia.handleInvocationResult(txID, info, res) } +func (ia *invokeApplier) handleInvokeScriptInvocationError( + invErr error, + txID crypto.Digest, + failedChanges txBalanceChanges, + info *fallibleValidationParams, +) (*applicationResult, error) { + handleError := func(rejectTx bool) (*applicationResult, error) { + if rejectTx { + return nil, errors.Wrapf( + invErr, "transaction rejected with spent complexity %d and following call stack:\n%s", + ride.EvaluationErrorSpentComplexity(invErr), + strings.Join(ride.EvaluationErrorCallStack(invErr), "\n"), + ) + } + res := &invocationResult{failed: true, code: proto.DAppError, text: invErr.Error(), changes: failedChanges} + return ia.handleInvocationResult(txID, info, res) + } + // Script returned error, it's OK, but we have to decide is it failed or rejected transaction. + // After activation of RideV6 feature transactions are failed if they are not cheap regardless the error kind. + var ( + isCheap = int(ia.sc.recentTxComplexity) <= FailFreeInvokeComplexity + rejectTx = !info.acceptFailed || isCheap // Reject transaction if no failed transactions or the transaction is cheap + ) + if info.rideV6Activated { + return handleError(rejectTx) + } + // Before RideV6 activation in the following cases the transaction is rejected: + // 1) Failing of transactions is not activated yet, reject everything + // 2) The error is ride.InternalInvocationError and correct fail/reject behaviour is activated + // 3) The spent complexity is less than limit + switch et := ride.GetEvaluationErrorType(invErr); et { + case ride.UserError, ride.RuntimeError: + // Usual script error produced by user code or system functions. + // We reject transaction if spent complexity is less than limit. + case ride.InternalInvocationError: + // Special script error produced by internal script invocation or application of results. + // Reject transaction after certain height + rejectTx = rejectTx || info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight + case ride.Undefined, ride.EvaluationFailure: // Unhandled or evaluator error + return nil, errors.Wrapf(invErr, "invocation of transaction '%s' failed with error type value (%v)", txID.String(), et) + default: + return nil, errors.Wrapf(invErr, "invocation of transaction '%s' failed with unknown error type value (%v)", txID.String(), et) + } + return handleError(rejectTx) +} + type invocationResult struct { failed bool code proto.TxFailureReason diff --git a/pkg/state/invoke_applier_test.go b/pkg/state/invoke_applier_test.go index 6c6500d8c..7f1c8f0a8 100644 --- a/pkg/state/invoke_applier_test.go +++ b/pkg/state/invoke_applier_test.go @@ -809,7 +809,7 @@ func TestFailedInvokeApplicationComplexity(t *testing.T) { // This transaction reaches data entries size limit (16 KB) after reaching 1000 complexity limit fcSizeLimitAfterComplexityLimit := proto.FunctionCall{Name: "keyvalue", Arguments: []proto.Argument{&proto.IntegerArgument{Value: 99}, &proto.StringArgument{Value: strings.Repeat("0", 150)}}} // This transaction reaches data entries size limit (16 KB) before reaching 1000 complexity limit - fcSizeLimitBeforeComplexityLimit := proto.FunctionCall{Name: "keyvalue", Arguments: []proto.Argument{&proto.IntegerArgument{Value: 11}, &proto.StringArgument{Value: strings.Repeat("0", 2000)}}} + fcSizeLimitBeforeComplexityLimit := proto.FunctionCall{Name: "keyvalue", Arguments: []proto.Argument{&proto.IntegerArgument{Value: 14}, &proto.StringArgument{Value: strings.Repeat("0", 2000)}}} tests := []invokeApplierTestData{ { payments: []proto.ScriptPayment{}, diff --git a/pkg/state/script_caller.go b/pkg/state/script_caller.go index 20bed3d63..a25f4f2f9 100644 --- a/pkg/state/script_caller.go +++ b/pkg/state/script_caller.go @@ -252,6 +252,7 @@ func (a *scriptCaller) invokeFunction(tree *ast.Tree, tx proto.Transaction, info payments, sender, info.blockV5Activated, + info.rideV5Activated, info.rideV6Activated, proto.IsProtobufTx(tx), tree.LibVersion, @@ -286,6 +287,7 @@ func (a *scriptCaller) invokeFunction(tree *ast.Tree, tx proto.Transaction, info payments, sender, info.blockV5Activated, + info.rideV5Activated, info.rideV6Activated, proto.IsProtobufTx(tx), tree.LibVersion, @@ -336,6 +338,7 @@ func (a *scriptCaller) invokeFunction(tree *ast.Tree, tx proto.Transaction, info payments, sender, info.blockV5Activated, + info.rideV5Activated, info.rideV6Activated, proto.IsProtobufTx(tx), tree.LibVersion,