diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 91652d5c4..e8df34de1 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1504,11 +1504,12 @@ func initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transac func (bc *Blockchain) VerifyWitness(h util.Uint160, c crypto.Verifiable, w *transaction.Witness, gas int64) error { ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) ic.Container = c - return bc.verifyHashAgainstScript(h, w, ic, gas) + _, err := bc.verifyHashAgainstScript(h, w, ic, gas) + return err } -// verifyHashAgainstScript verifies given hash against the given witness. -func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, gas int64) error { +// verifyHashAgainstScript verifies given hash against the given witness and returns the amount of GAS consumed. +func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, gas int64) (int64, error) { gasPolicy := bc.contracts.Policy.GetMaxVerificationGas(interopCtx.DAO) if gas > gasPolicy { gas = gasPolicy @@ -1518,28 +1519,28 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa vm.SetPriceGetter(getPrice) vm.GasLimit = gas if err := initVerificationVM(interopCtx, hash, witness); err != nil { - return err + return 0, err } err := vm.Run() if vm.HasFailed() { - return fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err) + return 0, fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err) } resEl := vm.Estack().Pop() if resEl != nil { res, err := resEl.Item().TryBool() if err != nil { - return fmt.Errorf("%w: invalid return value", ErrVerificationFailed) + return 0, fmt.Errorf("%w: invalid return value", ErrVerificationFailed) } if !res { - return fmt.Errorf("%w: invalid signature", ErrVerificationFailed) + return 0, fmt.Errorf("%w: invalid signature", ErrVerificationFailed) } if vm.Estack().Len() != 0 { - return fmt.Errorf("%w: expected exactly one returned value", ErrVerificationFailed) + return 0, fmt.Errorf("%w: expected exactly one returned value", ErrVerificationFailed) } } else { - return fmt.Errorf("%w: no result returned from the script", ErrVerificationFailed) + return 0, fmt.Errorf("%w: no result returned from the script", ErrVerificationFailed) } - return nil + return vm.GasConsumed(), nil } // verifyTxWitnesses verifies the scripts (witnesses) that come with a given @@ -1553,11 +1554,13 @@ func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block return fmt.Errorf("%w: %d vs %d", ErrTxInvalidWitnessNum, len(t.Signers), len(t.Scripts)) } interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, block, t) + gasLimit := t.NetworkFee for i := range t.Signers { - err := bc.verifyHashAgainstScript(t.Signers[i].Account, &t.Scripts[i], interopCtx, t.NetworkFee) + gasConsumed, err := bc.verifyHashAgainstScript(t.Signers[i].Account, &t.Scripts[i], interopCtx, gasLimit) if err != nil { return fmt.Errorf("witness #%d: %w", i, err) } + gasLimit -= gasConsumed } return nil diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 365dd14e4..2347f03cc 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -215,7 +215,7 @@ func TestVerifyTx(t *testing.T) { bc := newTestChain(t) defer bc.Close() - accs := make([]*wallet.Account, 3) + accs := make([]*wallet.Account, 4) for i := range accs { var err error accs[i], err = wallet.NewAccount() @@ -326,6 +326,16 @@ func TestVerifyTx(t *testing.T) { tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10] checkErr(t, ErrVerificationFailed, tx) }) + t.Run("InsufficientNetworkFeeForSecondWitness", func(t *testing.T) { + tx := bc.newTestTx(h, testScript) + tx.Signers = append(tx.Signers, transaction.Signer{ + Account: accs[3].PrivateKey().GetScriptHash(), + Scopes: transaction.Global, + }) + require.NoError(t, accs[0].SignTx(tx)) + require.NoError(t, accs[3].SignTx(tx)) + checkErr(t, ErrVerificationFailed, tx) + }) t.Run("OldTX", func(t *testing.T) { tx := bc.newTestTx(h, testScript) require.NoError(t, accs[0].SignTx(tx)) @@ -492,22 +502,22 @@ func TestVerifyHashAgainstScript(t *testing.T) { newH := cs.ScriptHash() newH[0] = ^newH[0] w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - err := bc.verifyHashAgainstScript(newH, w, ic, gas) + _, err := bc.verifyHashAgainstScript(newH, w, ic, gas) require.True(t, errors.Is(err, ErrUnknownVerificationContract)) }) t.Run("Invalid", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - err := bc.verifyHashAgainstScript(csInvalid.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(csInvalid.ScriptHash(), w, ic, gas) require.True(t, errors.Is(err, ErrInvalidVerificationContract)) }) t.Run("ValidSignature", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) require.NoError(t, err) }) t.Run("InvalidSignature", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}} - err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) require.True(t, errors.Is(err, ErrVerificationFailed)) }) }) @@ -517,7 +527,7 @@ func TestVerifyHashAgainstScript(t *testing.T) { InvocationScript: []byte{byte(opcode.NOP)}, VerificationScript: verif, } - err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, 1) + _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, 1) require.True(t, errors.Is(err, ErrVerificationFailed)) }) t.Run("NoResult", func(t *testing.T) { @@ -526,7 +536,7 @@ func TestVerifyHashAgainstScript(t *testing.T) { InvocationScript: []byte{byte(opcode.PUSH1)}, VerificationScript: verif, } - err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) + _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) require.True(t, errors.Is(err, ErrVerificationFailed)) }) t.Run("BadResult", func(t *testing.T) { @@ -537,7 +547,7 @@ func TestVerifyHashAgainstScript(t *testing.T) { InvocationScript: []byte{byte(opcode.NOP)}, VerificationScript: verif, } - err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) + _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) require.True(t, errors.Is(err, ErrVerificationFailed)) }) t.Run("TooManyResults", func(t *testing.T) { @@ -546,7 +556,7 @@ func TestVerifyHashAgainstScript(t *testing.T) { InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.PUSH1)}, VerificationScript: verif, } - err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) + _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) require.True(t, errors.Is(err, ErrVerificationFailed)) }) } diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index 7e6eeebb3..81786aff3 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -40,7 +40,7 @@ func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint if c.Scopes&transaction.CalledByEntry != 0 { callingScriptHash := v.GetCallingScriptHash() entryScriptHash := v.GetEntryScriptHash() - if callingScriptHash == entryScriptHash { + if callingScriptHash.Equals(util.Uint160{}) || callingScriptHash == entryScriptHash { return true, nil } }