Merge pull request #1318 from nspcc-dev/fix/verifytests
Add tests for `verifyTx` and `verifyHeader`
This commit is contained in:
commit
8e619cc671
4 changed files with 283 additions and 17 deletions
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -1179,24 +1180,41 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra
|
||||||
return txes
|
return txes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Various errors that could be returns upon header verification.
|
||||||
|
var (
|
||||||
|
ErrHdrHashMismatch = errors.New("previous header hash doesn't match")
|
||||||
|
ErrHdrIndexMismatch = errors.New("previous header index doesn't match")
|
||||||
|
ErrHdrInvalidTimestamp = errors.New("block is not newer than the previous one")
|
||||||
|
)
|
||||||
|
|
||||||
func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error {
|
func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error {
|
||||||
if prevHeader.Hash() != currHeader.PrevHash {
|
if prevHeader.Hash() != currHeader.PrevHash {
|
||||||
return errors.New("previous header hash doesn't match")
|
return ErrHdrHashMismatch
|
||||||
}
|
}
|
||||||
if prevHeader.Index+1 != currHeader.Index {
|
if prevHeader.Index+1 != currHeader.Index {
|
||||||
return errors.New("previous header index doesn't match")
|
return ErrHdrIndexMismatch
|
||||||
}
|
}
|
||||||
if prevHeader.Timestamp >= currHeader.Timestamp {
|
if prevHeader.Timestamp >= currHeader.Timestamp {
|
||||||
return errors.New("block is not newer than the previous one")
|
return ErrHdrInvalidTimestamp
|
||||||
}
|
}
|
||||||
return bc.verifyHeaderWitnesses(currHeader, prevHeader)
|
return bc.verifyHeaderWitnesses(currHeader, prevHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Various errors that could be returned upon verification.
|
||||||
|
var (
|
||||||
|
ErrTxExpired = errors.New("transaction has expired")
|
||||||
|
ErrInsufficientFunds = errors.New("insufficient funds")
|
||||||
|
ErrTxSmallNetworkFee = errors.New("too small network fee")
|
||||||
|
ErrTxTooBig = errors.New("too big transaction")
|
||||||
|
ErrMemPoolConflict = errors.New("invalid transaction due to conflicts with the memory pool")
|
||||||
|
ErrTxInvalidWitnessNum = errors.New("number of signers doesn't match witnesses")
|
||||||
|
)
|
||||||
|
|
||||||
// verifyTx verifies whether a transaction is bonafide or not.
|
// verifyTx verifies whether a transaction is bonafide or not.
|
||||||
func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) error {
|
func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) error {
|
||||||
height := bc.BlockHeight()
|
height := bc.BlockHeight()
|
||||||
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
||||||
return fmt.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height)
|
return fmt.Errorf("%w: ValidUntilBlock = %d, current height = %d", ErrTxExpired, t.ValidUntilBlock, height)
|
||||||
}
|
}
|
||||||
// Policying.
|
// Policying.
|
||||||
if err := bc.contracts.Policy.CheckPolicy(bc.dao, t); err != nil {
|
if err := bc.contracts.Policy.CheckPolicy(bc.dao, t); err != nil {
|
||||||
|
@ -1206,20 +1224,20 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
balance := bc.GetUtilityTokenBalance(t.Sender())
|
balance := bc.GetUtilityTokenBalance(t.Sender())
|
||||||
need := t.SystemFee + t.NetworkFee
|
need := t.SystemFee + t.NetworkFee
|
||||||
if balance.Cmp(big.NewInt(need)) < 0 {
|
if balance.Cmp(big.NewInt(need)) < 0 {
|
||||||
return fmt.Errorf("insufficient funds: balance is %v, need: %v", balance, need)
|
return fmt.Errorf("%w: balance is %v, need: %v", ErrInsufficientFunds, balance, need)
|
||||||
}
|
}
|
||||||
size := io.GetVarSize(t)
|
size := io.GetVarSize(t)
|
||||||
if size > transaction.MaxTransactionSize {
|
if size > transaction.MaxTransactionSize {
|
||||||
return fmt.Errorf("too big transaction (%d > MaxTransactionSize %d)", size, transaction.MaxTransactionSize)
|
return fmt.Errorf("%w: (%d > MaxTransactionSize %d)", ErrTxTooBig, size, transaction.MaxTransactionSize)
|
||||||
}
|
}
|
||||||
needNetworkFee := int64(size) * bc.FeePerByte()
|
needNetworkFee := int64(size) * bc.FeePerByte()
|
||||||
netFee := t.NetworkFee - needNetworkFee
|
netFee := t.NetworkFee - needNetworkFee
|
||||||
if netFee < 0 {
|
if netFee < 0 {
|
||||||
return fmt.Errorf("insufficient funds: net fee is %v, need %v", t.NetworkFee, needNetworkFee)
|
return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee)
|
||||||
}
|
}
|
||||||
if block == nil {
|
if block == nil {
|
||||||
if ok := bc.memPool.Verify(t, bc); !ok {
|
if ok := bc.memPool.Verify(t, bc); !ok {
|
||||||
return errors.New("invalid transaction due to conflicts with the memory pool")
|
return ErrMemPoolConflict
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1405,17 +1423,41 @@ func ScriptFromWitness(hash util.Uint160, witness *transaction.Witness) ([]byte,
|
||||||
emit.AppCall(bb.BinWriter, hash)
|
emit.AppCall(bb.BinWriter, hash)
|
||||||
verification = bb.Bytes()
|
verification = bb.Bytes()
|
||||||
} else if h := witness.ScriptHash(); hash != h {
|
} else if h := witness.ScriptHash(); hash != h {
|
||||||
return nil, errors.New("witness hash mismatch")
|
return nil, ErrWitnessHashMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
return verification, nil
|
return verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Various witness verification errors.
|
||||||
|
var (
|
||||||
|
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||||
|
ErrVerificationFailed = errors.New("signature check failed")
|
||||||
|
ErrUnknownVerificationContract = errors.New("unknown verification contract")
|
||||||
|
ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method")
|
||||||
|
)
|
||||||
|
|
||||||
// verifyHashAgainstScript verifies given hash against the given witness.
|
// verifyHashAgainstScript verifies given hash against the given witness.
|
||||||
func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, useKeys bool, gas int64) error {
|
func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, useKeys bool, gas int64) error {
|
||||||
verification, err := ScriptFromWitness(hash, witness)
|
var offset int
|
||||||
if err != nil {
|
var initMD *manifest.Method
|
||||||
return err
|
verification := witness.VerificationScript
|
||||||
|
if len(verification) != 0 {
|
||||||
|
if witness.ScriptHash() != hash {
|
||||||
|
return ErrWitnessHashMismatch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cs, err := interopCtx.DAO.GetContractState(hash)
|
||||||
|
if err != nil {
|
||||||
|
return ErrUnknownVerificationContract
|
||||||
|
}
|
||||||
|
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify)
|
||||||
|
if md == nil {
|
||||||
|
return ErrInvalidVerificationContract
|
||||||
|
}
|
||||||
|
verification = cs.Script
|
||||||
|
offset = md.Offset
|
||||||
|
initMD = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
||||||
}
|
}
|
||||||
|
|
||||||
gasPolicy := bc.contracts.Policy.GetMaxVerificationGas(interopCtx.DAO)
|
gasPolicy := bc.contracts.Policy.GetMaxVerificationGas(interopCtx.DAO)
|
||||||
|
@ -1427,6 +1469,10 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
vm.SetPriceGetter(getPrice)
|
vm.SetPriceGetter(getPrice)
|
||||||
vm.GasLimit = gas
|
vm.GasLimit = gas
|
||||||
vm.LoadScriptWithFlags(verification, smartcontract.NoneFlag)
|
vm.LoadScriptWithFlags(verification, smartcontract.NoneFlag)
|
||||||
|
vm.Jump(vm.Context(), offset)
|
||||||
|
if initMD != nil {
|
||||||
|
vm.Call(vm.Context(), initMD.Offset)
|
||||||
|
}
|
||||||
vm.LoadScript(witness.InvocationScript)
|
vm.LoadScript(witness.InvocationScript)
|
||||||
if useKeys {
|
if useKeys {
|
||||||
bc.keyCacheLock.RLock()
|
bc.keyCacheLock.RLock()
|
||||||
|
@ -1435,14 +1481,14 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
}
|
}
|
||||||
bc.keyCacheLock.RUnlock()
|
bc.keyCacheLock.RUnlock()
|
||||||
}
|
}
|
||||||
err = vm.Run()
|
err := vm.Run()
|
||||||
if vm.HasFailed() {
|
if vm.HasFailed() {
|
||||||
return fmt.Errorf("vm execution has failed: %w", err)
|
return fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err)
|
||||||
}
|
}
|
||||||
resEl := vm.Estack().Pop()
|
resEl := vm.Estack().Pop()
|
||||||
if resEl != nil {
|
if resEl != nil {
|
||||||
if !resEl.Bool() {
|
if !resEl.Bool() {
|
||||||
return fmt.Errorf("signature check failed")
|
return fmt.Errorf("%w: invalid signature", ErrVerificationFailed)
|
||||||
}
|
}
|
||||||
if useKeys {
|
if useKeys {
|
||||||
bc.keyCacheLock.RLock()
|
bc.keyCacheLock.RLock()
|
||||||
|
@ -1455,7 +1501,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("no result returned from the script")
|
return fmt.Errorf("%w: no result returned from the script", ErrVerificationFailed)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1468,7 +1514,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||||
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block) error {
|
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block) error {
|
||||||
if len(t.Signers) != len(t.Scripts) {
|
if len(t.Signers) != len(t.Scripts) {
|
||||||
return fmt.Errorf("number of signers doesn't match witnesses (%d vs %d)", len(t.Signers), len(t.Scripts))
|
return fmt.Errorf("%w: %d vs %d", ErrTxInvalidWitnessNum, len(t.Signers), len(t.Scripts))
|
||||||
}
|
}
|
||||||
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, block, t)
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, block, t)
|
||||||
for i := range t.Signers {
|
for i := range t.Signers {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -12,15 +15,46 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestVerifyHeader(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
prev := bc.topBlock.Load().(*block.Block).Header()
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
t.Run("Hash", func(t *testing.T) {
|
||||||
|
h := prev.Hash()
|
||||||
|
h[0] = ^h[0]
|
||||||
|
hdr := newBlock(bc.config, 1, h).Header()
|
||||||
|
require.True(t, errors.Is(bc.verifyHeader(hdr, prev), ErrHdrHashMismatch))
|
||||||
|
})
|
||||||
|
t.Run("Index", func(t *testing.T) {
|
||||||
|
hdr := newBlock(bc.config, 3, prev.Hash()).Header()
|
||||||
|
require.True(t, errors.Is(bc.verifyHeader(hdr, prev), ErrHdrIndexMismatch))
|
||||||
|
})
|
||||||
|
t.Run("Timestamp", func(t *testing.T) {
|
||||||
|
hdr := newBlock(bc.config, 1, prev.Hash()).Header()
|
||||||
|
hdr.Timestamp = 0
|
||||||
|
require.True(t, errors.Is(bc.verifyHeader(hdr, prev), ErrHdrInvalidTimestamp))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("Valid", func(t *testing.T) {
|
||||||
|
hdr := newBlock(bc.config, 1, prev.Hash()).Header()
|
||||||
|
require.NoError(t, bc.verifyHeader(hdr, prev))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddHeaders(t *testing.T) {
|
func TestAddHeaders(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
defer bc.Close()
|
defer bc.Close()
|
||||||
|
@ -145,6 +179,183 @@ func TestGetBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) newTestTx(h util.Uint160, script []byte) *transaction.Transaction {
|
||||||
|
tx := transaction.New(testchain.Network(), script, 1_000_000)
|
||||||
|
tx.Nonce = rand.Uint32()
|
||||||
|
tx.ValidUntilBlock = 100
|
||||||
|
tx.Signers = []transaction.Signer{{
|
||||||
|
Account: h,
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
}}
|
||||||
|
tx.NetworkFee = int64(io.GetVarSize(tx)+200 /* witness */) * bc.FeePerByte()
|
||||||
|
tx.NetworkFee += 1_000_000 // verification cost
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyTx(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
accs := make([]*wallet.Account, 2)
|
||||||
|
for i := range accs {
|
||||||
|
var err error
|
||||||
|
accs[i], err = wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
neoHash := bc.contracts.NEO.Hash
|
||||||
|
gasHash := bc.contracts.GAS.Hash
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for _, sc := range []util.Uint160{neoHash, gasHash} {
|
||||||
|
for _, a := range accs {
|
||||||
|
amount := int64(1_000_000)
|
||||||
|
if sc.Equals(gasHash) {
|
||||||
|
amount = 1_000_000_000
|
||||||
|
}
|
||||||
|
emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer",
|
||||||
|
neoOwner, a.PrivateKey().GetScriptHash(), amount)
|
||||||
|
emit.Opcode(w.BinWriter, opcode.ASSERT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
|
||||||
|
txMove := bc.newTestTx(neoOwner, w.Bytes())
|
||||||
|
txMove.SystemFee = 1_000_000_000
|
||||||
|
require.NoError(t, signTx(bc, txMove))
|
||||||
|
b := bc.newBlock(txMove)
|
||||||
|
require.NoError(t, bc.AddBlock(b))
|
||||||
|
|
||||||
|
aer, err := bc.GetAppExecResult(txMove.Hash())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, aer.VMState, vm.HaltState)
|
||||||
|
|
||||||
|
res, err := invokeNativePolicyMethod(bc, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
|
|
||||||
|
checkErr := func(t *testing.T, expectedErr error, tx *transaction.Transaction) {
|
||||||
|
err := bc.verifyTx(tx, nil)
|
||||||
|
fmt.Println(err)
|
||||||
|
require.True(t, errors.Is(err, expectedErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
testScript := []byte{byte(opcode.PUSH1)}
|
||||||
|
h := accs[0].PrivateKey().GetScriptHash()
|
||||||
|
t.Run("Expired", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.ValidUntilBlock = 1
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, ErrTxExpired, tx)
|
||||||
|
})
|
||||||
|
t.Run("BlockedAccount", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(accs[1].PrivateKey().GetScriptHash(), testScript)
|
||||||
|
require.NoError(t, accs[1].SignTx(tx))
|
||||||
|
err := bc.verifyTx(tx, nil)
|
||||||
|
require.True(t, errors.Is(err, ErrPolicy))
|
||||||
|
})
|
||||||
|
t.Run("InsufficientGas", func(t *testing.T) {
|
||||||
|
balance := bc.GetUtilityTokenBalance(h)
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.SystemFee = balance.Int64() + 1
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, ErrInsufficientFunds, tx)
|
||||||
|
})
|
||||||
|
t.Run("TooBigTx", func(t *testing.T) {
|
||||||
|
script := make([]byte, transaction.MaxTransactionSize)
|
||||||
|
tx := bc.newTestTx(h, script)
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, ErrTxTooBig, tx)
|
||||||
|
})
|
||||||
|
t.Run("SmallNetworkFee", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.NetworkFee = 1
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, ErrTxSmallNetworkFee, tx)
|
||||||
|
})
|
||||||
|
t.Run("Conflict", func(t *testing.T) {
|
||||||
|
balance := bc.GetUtilityTokenBalance(h).Int64()
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.NetworkFee = balance / 2
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, nil, tx)
|
||||||
|
|
||||||
|
tx2 := bc.newTestTx(h, testScript)
|
||||||
|
tx2.NetworkFee = balance / 2
|
||||||
|
require.NoError(t, bc.memPool.Add(tx2, bc))
|
||||||
|
checkErr(t, ErrMemPoolConflict, tx)
|
||||||
|
})
|
||||||
|
t.Run("NotEnoughWitnesses", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
checkErr(t, ErrTxInvalidWitnessNum, tx)
|
||||||
|
})
|
||||||
|
t.Run("InvalidWitnessHash", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
tx.Scripts[0].VerificationScript = []byte{byte(opcode.PUSHT)}
|
||||||
|
checkErr(t, ErrWitnessHashMismatch, tx)
|
||||||
|
})
|
||||||
|
t.Run("InvalidWitnessSignature", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10]
|
||||||
|
checkErr(t, ErrVerificationFailed, tx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyHashAgainstScript(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
cs, csInvalid := getTestContractState()
|
||||||
|
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(cs))
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(csInvalid))
|
||||||
|
|
||||||
|
gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO)
|
||||||
|
t.Run("Contract", func(t *testing.T) {
|
||||||
|
t.Run("Missing", func(t *testing.T) {
|
||||||
|
newH := cs.ScriptHash()
|
||||||
|
newH[0] = ^newH[0]
|
||||||
|
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
|
||||||
|
err := bc.verifyHashAgainstScript(newH, w, ic, false, 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, false, 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, false, 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, false, gas)
|
||||||
|
require.True(t, errors.Is(err, ErrVerificationFailed))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("NotEnoughGas", func(t *testing.T) {
|
||||||
|
verif := []byte{byte(opcode.PUSH1)}
|
||||||
|
w := &transaction.Witness{
|
||||||
|
InvocationScript: []byte{byte(opcode.NOP)},
|
||||||
|
VerificationScript: verif,
|
||||||
|
}
|
||||||
|
err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, false, 1)
|
||||||
|
require.True(t, errors.Is(err, ErrVerificationFailed))
|
||||||
|
})
|
||||||
|
t.Run("NoResult", func(t *testing.T) {
|
||||||
|
verif := []byte{byte(opcode.DROP)}
|
||||||
|
w := &transaction.Witness{
|
||||||
|
InvocationScript: []byte{byte(opcode.PUSH1)},
|
||||||
|
VerificationScript: verif,
|
||||||
|
}
|
||||||
|
err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, false, gas)
|
||||||
|
require.True(t, errors.Is(err, ErrVerificationFailed))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasBlock(t *testing.T) {
|
func TestHasBlock(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
blocks, err := bc.genBlocks(50)
|
blocks, err := bc.genBlocks(50)
|
||||||
|
|
|
@ -337,6 +337,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
|
||||||
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET),
|
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET),
|
||||||
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET),
|
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET),
|
||||||
byte(opcode.RET),
|
byte(opcode.RET),
|
||||||
|
byte(opcode.LDSFLD0), byte(opcode.SUB), byte(opcode.CONVERT), byte(stackitem.BooleanT), byte(opcode.RET),
|
||||||
}
|
}
|
||||||
h := hash.Hash160(script)
|
h := hash.Hash160(script)
|
||||||
m := manifest.NewManifest(h)
|
m := manifest.NewManifest(h)
|
||||||
|
@ -384,6 +385,11 @@ func getTestContractState() (*state.Contract, *state.Contract) {
|
||||||
Offset: 18,
|
Offset: 18,
|
||||||
ReturnType: smartcontract.IntegerType,
|
ReturnType: smartcontract.IntegerType,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: manifest.MethodVerify,
|
||||||
|
Offset: 19,
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
Script: script,
|
Script: script,
|
||||||
|
|
|
@ -15,6 +15,9 @@ const (
|
||||||
// MethodInit is a name for default initialization method.
|
// MethodInit is a name for default initialization method.
|
||||||
MethodInit = "_initialize"
|
MethodInit = "_initialize"
|
||||||
|
|
||||||
|
// MethodVerify is a name for default verification method.
|
||||||
|
MethodVerify = "verify"
|
||||||
|
|
||||||
// NEP5StandardName represents the name of NEP5 smartcontract standard.
|
// NEP5StandardName represents the name of NEP5 smartcontract standard.
|
||||||
NEP5StandardName = "NEP-5"
|
NEP5StandardName = "NEP-5"
|
||||||
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
||||||
|
|
Loading…
Reference in a new issue