neoneo-go/pkg/core/blockchain_test.go
Anna Shaleva cd42b8b20c core: allow early Seek stop
This simple approach allows to improve the performance of
BoltDB and LevelDB in both terms of speed and allocations
for retrieving GasPerVote value from the storage.
MemoryPS's speed suffers a bit, but we don't use it for
production environment.

Part of #2322.

Benchmark results:

name                                                              old time/op    new time/op    delta
NEO_GetGASPerVote/MemPS_10RewardRecords_1RewardDistance-8           25.3µs ± 1%    26.4µs ± 9%   +4.41%  (p=0.043 n=10+9)
NEO_GetGASPerVote/MemPS_10RewardRecords_10RewardDistance-8          27.9µs ± 1%    30.1µs ±15%   +7.97%  (p=0.000 n=10+9)
NEO_GetGASPerVote/MemPS_10RewardRecords_100RewardDistance-8         55.1µs ± 1%    60.2µs ± 7%   +9.27%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_10RewardRecords_1000RewardDistance-8         353µs ± 2%     416µs ±13%  +17.88%  (p=0.000 n=8+8)
NEO_GetGASPerVote/MemPS_100RewardRecords_1RewardDistance-8           195µs ± 1%     216µs ± 7%  +10.42%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_10RewardDistance-8          200µs ± 4%     214µs ± 9%   +6.99%  (p=0.002 n=9+8)
NEO_GetGASPerVote/MemPS_100RewardRecords_100RewardDistance-8         223µs ± 2%     247µs ± 9%  +10.60%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_1000RewardDistance-8        612µs ±23%     855µs ±52%  +39.60%  (p=0.001 n=9+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1RewardDistance-8         11.3ms ±53%    10.7ms ±50%     ~     (p=0.739 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_10RewardDistance-8        12.0ms ±37%    10.4ms ±65%     ~     (p=0.853 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_100RewardDistance-8       11.3ms ±40%    10.4ms ±49%     ~     (p=0.631 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1000RewardDistance-8      3.80ms ±45%    3.69ms ±27%     ~     (p=0.931 n=6+5)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1RewardDistance-8          23.0µs ± 9%    22.6µs ± 4%     ~     (p=0.059 n=8+9)
NEO_GetGASPerVote/BoltPS_10RewardRecords_10RewardDistance-8         25.9µs ± 5%    24.8µs ± 4%   -4.17%  (p=0.006 n=10+8)
NEO_GetGASPerVote/BoltPS_10RewardRecords_100RewardDistance-8        42.7µs ±13%    38.9µs ± 1%   -8.85%  (p=0.000 n=9+8)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1000RewardDistance-8       80.8µs ±12%    84.9µs ± 9%     ~     (p=0.114 n=8+9)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1RewardDistance-8         64.3µs ±16%    22.1µs ±23%  -65.64%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_10RewardDistance-8        61.0µs ±34%    23.2µs ± 8%  -62.04%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_100RewardRecords_100RewardDistance-8       62.2µs ±14%    25.7µs ±13%  -58.66%  (p=0.000 n=9+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1000RewardDistance-8       359µs ±60%     325µs ±60%     ~     (p=0.739 n=10+10)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1RewardDistance-8         242µs ±21%      13µs ±28%  -94.49%  (p=0.000 n=10+8)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_10RewardDistance-8        229µs ±23%      18µs ±70%  -92.02%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_100RewardDistance-8       238µs ±28%     20µs ±109%  -91.38%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1000RewardDistance-8      265µs ±20%      77µs ±62%  -71.04%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1RewardDistance-8         25.5µs ± 3%    24.7µs ± 7%     ~     (p=0.143 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_10RewardDistance-8        27.4µs ± 2%    27.9µs ± 6%     ~     (p=0.280 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_100RewardDistance-8       50.2µs ± 7%    47.4µs ±10%     ~     (p=0.156 n=9+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1000RewardDistance-8      98.2µs ± 9%    94.6µs ±10%     ~     (p=0.218 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1RewardDistance-8        82.9µs ±13%    32.1µs ±22%  -61.30%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_10RewardDistance-8       92.2µs ±11%    33.7µs ±12%  -63.42%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_100RewardRecords_100RewardDistance-8      88.3µs ±22%    39.4µs ±14%  -55.36%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1000RewardDistance-8      106µs ±18%      78µs ±24%  -26.20%  (p=0.000 n=9+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1RewardDistance-8        360µs ±24%      29µs ±53%  -91.91%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_10RewardDistance-8       353µs ±16%      50µs ±70%  -85.72%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_100RewardDistance-8      381µs ±20%     47µs ±111%  -87.64%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1000RewardDistance-8     434µs ±19%     113µs ±41%  -74.04%  (p=0.000 n=10+10)

name                                                              old alloc/op   new alloc/op   delta
NEO_GetGASPerVote/MemPS_10RewardRecords_1RewardDistance-8           4.82kB ± 0%    4.26kB ± 1%  -11.62%  (p=0.000 n=10+9)
NEO_GetGASPerVote/MemPS_10RewardRecords_10RewardDistance-8          4.99kB ± 0%    4.41kB ± 1%  -11.56%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_10RewardRecords_100RewardDistance-8         8.45kB ± 0%    7.87kB ± 0%   -6.88%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_10RewardRecords_1000RewardDistance-8        55.0kB ± 0%    54.5kB ± 0%   -0.81%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_1RewardDistance-8          29.1kB ± 0%    21.7kB ± 2%  -25.56%  (p=0.000 n=9+9)
NEO_GetGASPerVote/MemPS_100RewardRecords_10RewardDistance-8         29.3kB ± 1%    21.8kB ± 2%  -25.74%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_100RewardDistance-8        31.3kB ± 1%    23.6kB ± 1%  -24.50%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_1000RewardDistance-8       92.5kB ± 5%    84.7kB ± 3%   -8.50%  (p=0.000 n=10+9)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1RewardDistance-8          324kB ±29%     222kB ±44%  -31.33%  (p=0.007 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_10RewardDistance-8         308kB ±32%     174kB ±14%  -43.56%  (p=0.000 n=10+8)
NEO_GetGASPerVote/MemPS_1000RewardRecords_100RewardDistance-8        298kB ±23%     178kB ±36%  -40.26%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1000RewardDistance-8       362kB ± 6%     248kB ± 6%  -31.54%  (p=0.004 n=6+5)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1RewardDistance-8          5.15kB ± 3%    4.64kB ± 2%   -9.92%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_10RewardRecords_10RewardDistance-8         5.36kB ± 1%    4.75kB ± 5%  -11.42%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_10RewardRecords_100RewardDistance-8        8.15kB ± 4%    7.53kB ± 1%   -7.62%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1000RewardDistance-8       33.2kB ± 5%    33.2kB ± 7%     ~     (p=0.829 n=8+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1RewardDistance-8         20.1kB ± 7%     5.8kB ±13%  -70.90%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_10RewardDistance-8        19.8kB ±14%     6.2kB ± 5%  -68.87%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_100RewardRecords_100RewardDistance-8       21.7kB ± 6%     8.0kB ± 7%  -63.20%  (p=0.000 n=9+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1000RewardDistance-8      98.5kB ±44%    81.8kB ±48%     ~     (p=0.143 n=10+10)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1RewardDistance-8         130kB ± 4%       4kB ± 9%  -96.69%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_10RewardDistance-8        131kB ± 4%       5kB ±21%  -96.48%  (p=0.000 n=9+9)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_100RewardDistance-8       132kB ± 4%       6kB ±10%  -95.39%  (p=0.000 n=10+8)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1000RewardDistance-8      151kB ± 4%      26kB ±10%  -82.46%  (p=0.000 n=9+9)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1RewardDistance-8         5.92kB ± 3%    5.32kB ± 2%  -10.01%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_10RewardDistance-8        6.09kB ± 2%    5.48kB ± 2%  -10.00%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_100RewardDistance-8       9.61kB ± 1%    9.00kB ± 0%   -6.29%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1000RewardDistance-8      33.4kB ± 7%    32.2kB ± 5%   -3.60%  (p=0.037 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1RewardDistance-8        22.3kB ±10%     9.0kB ±16%  -59.78%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_10RewardDistance-8       23.6kB ± 6%     8.5kB ±20%  -63.76%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_100RewardDistance-8      24.2kB ± 9%    11.5kB ± 4%  -52.34%  (p=0.000 n=10+8)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1000RewardDistance-8     44.2kB ± 6%    30.8kB ± 9%  -30.24%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1RewardDistance-8        144kB ± 4%      10kB ±24%  -93.39%  (p=0.000 n=9+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_10RewardDistance-8       146kB ± 1%      11kB ±37%  -92.14%  (p=0.000 n=7+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_100RewardDistance-8      149kB ± 3%      11kB ±12%  -92.28%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1000RewardDistance-8     171kB ± 4%      34kB ±12%  -80.00%  (p=0.000 n=10+10)

name                                                              old allocs/op  new allocs/op  delta
NEO_GetGASPerVote/MemPS_10RewardRecords_1RewardDistance-8             95.0 ± 0%      74.0 ± 0%  -22.11%  (p=0.001 n=8+9)
NEO_GetGASPerVote/MemPS_10RewardRecords_10RewardDistance-8             100 ± 0%        78 ± 1%  -21.70%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_10RewardRecords_100RewardDistance-8            153 ± 0%       131 ± 2%  -14.25%  (p=0.000 n=6+10)
NEO_GetGASPerVote/MemPS_10RewardRecords_1000RewardDistance-8           799 ± 2%       797 ± 4%     ~     (p=0.956 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_1RewardDistance-8             438 ± 6%       167 ± 0%  -61.86%  (p=0.000 n=10+9)
NEO_GetGASPerVote/MemPS_100RewardRecords_10RewardDistance-8            446 ± 5%       172 ± 0%  -61.38%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_100RewardDistance-8           506 ± 4%       232 ± 1%  -54.21%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_100RewardRecords_1000RewardDistance-8        1.31k ± 5%     0.97k ± 4%  -26.20%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1RewardDistance-8          5.06k ± 1%     1.09k ± 2%  -78.53%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_10RewardDistance-8         5.02k ± 3%     1.08k ± 0%  -78.45%  (p=0.000 n=10+8)
NEO_GetGASPerVote/MemPS_1000RewardRecords_100RewardDistance-8        5.09k ± 3%     1.15k ± 2%  -77.48%  (p=0.000 n=10+10)
NEO_GetGASPerVote/MemPS_1000RewardRecords_1000RewardDistance-8       5.83k ± 1%     1.87k ± 3%  -68.02%  (p=0.004 n=6+5)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1RewardDistance-8             103 ± 2%        82 ± 1%  -20.83%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_10RewardRecords_10RewardDistance-8            107 ± 0%        86 ± 0%  -19.63%  (p=0.000 n=8+8)
NEO_GetGASPerVote/BoltPS_10RewardRecords_100RewardDistance-8           164 ± 1%       139 ± 0%  -15.45%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_10RewardRecords_1000RewardDistance-8          820 ± 1%       789 ± 1%   -3.70%  (p=0.000 n=9+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1RewardDistance-8            475 ± 0%        94 ± 3%  -80.15%  (p=0.000 n=10+9)
NEO_GetGASPerVote/BoltPS_100RewardRecords_10RewardDistance-8           481 ± 0%       100 ± 2%  -79.26%  (p=0.000 n=9+9)
NEO_GetGASPerVote/BoltPS_100RewardRecords_100RewardDistance-8          549 ± 0%       161 ± 2%  -70.69%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_100RewardRecords_1000RewardDistance-8       1.61k ±19%     1.19k ±25%  -26.05%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1RewardDistance-8         4.12k ± 0%     0.08k ± 2%  -98.02%  (p=0.000 n=10+10)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_10RewardDistance-8        4.14k ± 0%     0.09k ± 3%  -97.90%  (p=0.000 n=9+9)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_100RewardDistance-8       4.19k ± 0%     0.15k ± 3%  -96.52%  (p=0.000 n=9+9)
NEO_GetGASPerVote/BoltPS_1000RewardRecords_1000RewardDistance-8      4.82k ± 1%     0.74k ± 1%  -84.58%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1RewardDistance-8            112 ± 4%        90 ± 3%  -19.45%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_10RewardDistance-8           116 ± 2%        95 ± 2%  -17.90%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_100RewardDistance-8          170 ± 3%       148 ± 3%  -12.99%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_10RewardRecords_1000RewardDistance-8         800 ± 2%       772 ± 2%   -3.50%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1RewardDistance-8           480 ± 3%       118 ± 3%  -75.32%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_100RewardRecords_10RewardDistance-8          479 ± 2%       123 ± 3%  -74.33%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_100RewardRecords_100RewardDistance-8         542 ± 1%       183 ± 3%  -66.34%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_100RewardRecords_1000RewardDistance-8      1.19k ± 1%     0.79k ± 1%  -33.41%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1RewardDistance-8        4.21k ± 1%     0.13k ±21%  -96.83%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_10RewardDistance-8       4.23k ± 1%     0.15k ±17%  -96.48%  (p=0.000 n=10+10)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_100RewardDistance-8      4.27k ± 0%     0.19k ± 6%  -95.51%  (p=0.000 n=10+9)
NEO_GetGASPerVote/LevelPS_1000RewardRecords_1000RewardDistance-8     4.89k ± 1%     0.79k ± 2%  -83.80%  (p=0.000 n=10+10)
2022-01-19 20:54:35 +03:00

1836 lines
64 KiB
Go

package core
import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"math/rand"
"path/filepath"
"strings"
"testing"
"time"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"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/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result/subscriptions"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"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/slice"
"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/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/require"
"go.uber.org/zap/zaptest"
)
func TestVerifyHeader(t *testing.T) {
bc := newTestChain(t)
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) {
bc := newTestChain(t)
lastBlock := bc.topBlock.Load().(*block.Block)
h1 := newBlock(bc.config, 1, lastBlock.Hash()).Header
h2 := newBlock(bc.config, 2, h1.Hash()).Header
h3 := newBlock(bc.config, 3, h2.Hash()).Header
require.NoError(t, bc.AddHeaders())
require.NoError(t, bc.AddHeaders(&h1, &h2))
require.NoError(t, bc.AddHeaders(&h2, &h3))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
// Add them again, they should not be added.
require.NoError(t, bc.AddHeaders(&h3, &h2, &h1))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
h4 := newBlock(bc.config, 4, h3.Hash().Reverse()).Header
h5 := newBlock(bc.config, 5, h4.Hash()).Header
assert.Error(t, bc.AddHeaders(&h4, &h5))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
h6 := newBlock(bc.config, 4, h3.Hash()).Header
h6.Script.InvocationScript = nil
assert.Error(t, bc.AddHeaders(&h6))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
}
func TestAddBlock(t *testing.T) {
const size = 3
bc := newTestChain(t)
blocks, err := bc.genBlocks(size)
require.NoError(t, err)
lastBlock := blocks[len(blocks)-1]
assert.Equal(t, lastBlock.Index, bc.HeaderHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
// This one tests persisting blocks, so it does need to persist()
_, err = bc.persist(false)
require.NoError(t, err)
for _, block := range blocks {
key := storage.AppendPrefix(storage.DataExecutable, block.Hash().BytesBE())
_, err := bc.dao.Store.Get(key)
require.NoErrorf(t, err, "block %s not persisted", block.Hash())
}
assert.Equal(t, lastBlock.Index, bc.BlockHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
}
func TestAddBlockStateRoot(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.StateRootInHeader = true
})
sr, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight())
require.NoError(t, err)
tx := newNEP17Transfer(bc.contracts.NEO.Hash, neoOwner, util.Uint160{}, 1)
tx.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(neoOwner, tx)
require.NoError(t, testchain.SignTx(bc, tx))
lastBlock := bc.topBlock.Load().(*block.Block)
b := newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), tx)
err = bc.AddBlock(b)
require.True(t, errors.Is(err, ErrHdrStateRootSetting), "got: %v", err)
u := sr.Root
u[0] ^= 0xFF
b = newBlockWithState(bc.config, lastBlock.Index+1, lastBlock.Hash(), &u, tx)
err = bc.AddBlock(b)
require.True(t, errors.Is(err, ErrHdrInvalidStateRoot), "got: %v", err)
b = bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
}
func TestAddHeadersStateRoot(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.StateRootInHeader = true
})
r := bc.stateRoot.CurrentLocalStateRoot()
h1 := bc.newBlock().Header
// invalid stateroot
h1.PrevStateRoot[0] ^= 0xFF
require.True(t, errors.Is(bc.AddHeaders(&h1), ErrHdrInvalidStateRoot))
// valid stateroot
h1.PrevStateRoot = r
require.NoError(t, bc.AddHeaders(&h1))
// unable to verify stateroot (stateroot is computed for block #0 only => can
// verify stateroot of header #1 only) => just store the header
h2 := newBlockWithState(bc.config, 2, h1.Hash(), nil).Header
require.NoError(t, bc.AddHeaders(&h2))
}
func TestAddBadBlock(t *testing.T) {
bc := newTestChain(t)
// It has ValidUntilBlock == 0, which is wrong
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Signers = []transaction.Signer{{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
}}
require.NoError(t, testchain.SignTx(bc, tx))
b1 := bc.newBlock(tx)
require.Error(t, bc.AddBlock(b1))
bc.config.VerifyTransactions = false
require.NoError(t, bc.AddBlock(b1))
b2 := bc.newBlock()
b2.PrevHash = util.Uint256{}
require.Error(t, bc.AddBlock(b2))
bc.config.VerifyBlocks = false
require.NoError(t, bc.AddBlock(b2))
tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.ValidUntilBlock = 128
tx.Signers = []transaction.Signer{{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
}}
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.PoolTx(tx))
bc.config.VerifyTransactions = true
bc.config.VerifyBlocks = true
b3 := bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b3))
}
func TestGetHeader(t *testing.T) {
bc := newTestChain(t)
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(neoOwner, tx)
assert.Nil(t, testchain.SignTx(bc, tx))
block := bc.newBlock(tx)
err := bc.AddBlock(block)
assert.Nil(t, err)
// Test unpersisted and persisted access
for i := 0; i < 2; i++ {
hash := block.Hash()
header, err := bc.GetHeader(hash)
require.NoError(t, err)
assert.Equal(t, &block.Header, header)
b2 := bc.newBlock()
_, err = bc.GetHeader(b2.Hash())
assert.Error(t, err)
_, err = bc.persist(false)
assert.NoError(t, err)
}
}
func TestGetBlock(t *testing.T) {
bc := newTestChain(t)
blocks, err := bc.genBlocks(100)
require.NoError(t, err)
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
for i := 0; i < len(blocks); i++ {
block, err := bc.GetBlock(blocks[i].Hash())
require.NoErrorf(t, err, "can't get block %d: %s, attempt %d", i, err, j)
assert.Equal(t, blocks[i].Index, block.Index)
assert.Equal(t, blocks[i].Hash(), block.Hash())
}
_, err = bc.persist(false)
assert.NoError(t, err)
}
t.Run("store only header", func(t *testing.T) {
t.Run("non-empty block", func(t *testing.T) {
tx, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash,
random.Uint160(), 1, 1, 1000)
require.NoError(t, err)
b := bc.newBlock(tx)
require.NoError(t, bc.AddHeaders(&b.Header))
_, err = bc.GetBlock(b.Hash())
require.Error(t, err)
_, err = bc.GetHeader(b.Hash())
require.NoError(t, err)
require.NoError(t, bc.AddBlock(b))
_, err = bc.GetBlock(b.Hash())
require.NoError(t, err)
})
t.Run("empty block", func(t *testing.T) {
b := bc.newBlock()
require.NoError(t, bc.AddHeaders(&b.Header))
_, err = bc.GetBlock(b.Hash())
require.NoError(t, err)
})
})
}
func (bc *Blockchain) newTestTx(h util.Uint160, script []byte) *transaction.Transaction {
tx := transaction.New(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)
accs := make([]*wallet.Account, 5)
for i := range accs {
var err error
accs[i], err = wallet.NewAccount()
require.NoError(t, err)
}
oracleAcc := accs[2]
oraclePubs := keys.PublicKeys{oracleAcc.PrivateKey().PublicKey()}
require.NoError(t, oracleAcc.ConvertMultisig(1, oraclePubs))
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.AppCall(w.BinWriter, sc, "transfer", callflag.All,
neoOwner, a.Contract.ScriptHash(), amount, nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
}
emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All,
neoOwner, testchain.CommitteeScriptHash(), int64(1_000_000_000), nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(t, w.Err)
txMove := bc.newTestTx(neoOwner, w.Bytes())
txMove.SystemFee = 1_000_000_000
require.NoError(t, testchain.SignTx(bc, txMove))
b := bc.newBlock(txMove)
require.NoError(t, bc.AddBlock(b))
aer, err := bc.GetAppExecResults(txMove.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, aer[0].VMState, vm.HaltState)
res, err := invokeContractMethodGeneric(bc, 100000000, bc.contracts.Policy.Hash, "blockAccount", true, 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)
require.True(t, errors.Is(err, expectedErr), "expected: %v, got: %v", expectedErr, err)
}
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(netmode.UnitTestNet, 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(netmode.UnitTestNet, tx))
err := bc.VerifyTx(tx)
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(netmode.UnitTestNet, 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(netmode.UnitTestNet, tx))
checkErr(t, ErrTxTooBig, tx)
})
t.Run("NetworkFee", func(t *testing.T) {
t.Run("SmallNetworkFee", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
tx.NetworkFee = 1
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrTxSmallNetworkFee, tx)
})
t.Run("AlmostEnoughNetworkFee", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script)
expectedSize := io.GetVarSize(tx) + calcultedScriptSize
calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte()
tx.NetworkFee = calculatedNetFee - 1
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
require.Equal(t, expectedSize, io.GetVarSize(tx))
checkErr(t, ErrVerificationFailed, tx)
})
t.Run("EnoughNetworkFee", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script)
expectedSize := io.GetVarSize(tx) + calcultedScriptSize
calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte()
tx.NetworkFee = calculatedNetFee
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
require.Equal(t, expectedSize, io.GetVarSize(tx))
require.NoError(t, bc.VerifyTx(tx))
})
t.Run("CalculateNetworkFee, signature script", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
expectedSize := io.GetVarSize(tx)
verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script)
expectedSize += calculatedScriptSize
expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte()
tx.NetworkFee = expectedNetFee
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
actualSize := io.GetVarSize(tx)
require.Equal(t, expectedSize, actualSize)
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, tx)
gasConsumed, err := bc.verifyHashAgainstScript(h, &tx.Scripts[0], interopCtx, -1)
require.NoError(t, err)
require.Equal(t, verificationNetFee, gasConsumed)
require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed)
})
t.Run("CalculateNetworkFee, multisignature script", func(t *testing.T) {
multisigAcc := accs[4]
pKeys := keys.PublicKeys{multisigAcc.PrivateKey().PublicKey()}
require.NoError(t, multisigAcc.ConvertMultisig(1, pKeys))
multisigHash := hash.Hash160(multisigAcc.Contract.Script)
tx := bc.newTestTx(multisigHash, testScript)
verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), multisigAcc.Contract.Script)
expectedSize := io.GetVarSize(tx) + calculatedScriptSize
expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte()
tx.NetworkFee = expectedNetFee
require.NoError(t, multisigAcc.SignTx(netmode.UnitTestNet, tx))
actualSize := io.GetVarSize(tx)
require.Equal(t, expectedSize, actualSize)
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, tx)
gasConsumed, err := bc.verifyHashAgainstScript(multisigHash, &tx.Scripts[0], interopCtx, -1)
require.NoError(t, err)
require.Equal(t, verificationNetFee, gasConsumed)
require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed)
})
})
t.Run("InvalidTxScript", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
tx.Script = append(tx.Script, 0xff)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidScript, tx)
})
t.Run("InvalidVerificationScript", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
verif := []byte{byte(opcode.JMP), 3, 0xff, byte(opcode.PUSHT)}
tx.Signers = append(tx.Signers, transaction.Signer{
Account: hash.Hash160(verif),
Scopes: transaction.Global,
})
tx.NetworkFee += 1000000
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{
InvocationScript: []byte{},
VerificationScript: verif,
})
checkErr(t, ErrInvalidVerification, tx)
})
t.Run("InvalidInvocationScript", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
verif := []byte{byte(opcode.PUSHT)}
tx.Signers = append(tx.Signers, transaction.Signer{
Account: hash.Hash160(verif),
Scopes: transaction.Global,
})
tx.NetworkFee += 1000000
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{
InvocationScript: []byte{byte(opcode.JMP), 3, 0xff},
VerificationScript: verif,
})
checkErr(t, ErrInvalidInvocation, 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(netmode.UnitTestNet, tx))
require.NoError(t, bc.PoolTx(tx))
tx2 := bc.newTestTx(h, testScript)
tx2.NetworkFee = balance / 2
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2))
err := bc.PoolTx(tx2)
require.True(t, errors.Is(err, ErrMemPoolConflict))
})
t.Run("InvalidWitnessHash", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, 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(netmode.UnitTestNet, tx))
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(netmode.UnitTestNet, tx))
require.NoError(t, accs[3].SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrVerificationFailed, tx)
})
t.Run("OldTX", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
b := bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
err := bc.VerifyTx(tx)
require.True(t, errors.Is(err, ErrAlreadyExists))
})
t.Run("MemPooledTX", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
require.NoError(t, bc.PoolTx(tx))
err := bc.PoolTx(tx)
require.True(t, errors.Is(err, ErrAlreadyExists))
})
t.Run("MemPoolOOM", func(t *testing.T) {
bc.memPool = mempool.New(1, 0, false)
tx1 := bc.newTestTx(h, testScript)
tx1.NetworkFee += 10000 // Give it more priority.
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx1))
require.NoError(t, bc.PoolTx(tx1))
tx2 := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2))
err := bc.PoolTx(tx2)
require.True(t, errors.Is(err, ErrOOM))
})
t.Run("Attribute", func(t *testing.T) {
t.Run("InvalidHighPriority", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority})
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
t.Run("ValidHighPriority", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority})
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
}}
rawScript := testchain.CommitteeVerificationScript()
require.NoError(t, err)
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: rawScript,
}}
require.NoError(t, bc.VerifyTx(tx))
})
t.Run("Oracle", func(t *testing.T) {
orc := bc.contracts.Oracle
req := &state.OracleRequest{GasForResponse: 1000_0000}
require.NoError(t, orc.PutRequestInternal(1, req, bc.dao))
oracleScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(oraclePubs)
require.NoError(t, err)
oracleHash := hash.Hash160(oracleScript)
// We need to create new transaction,
// because hashes are cached after signing.
getOracleTx := func(t *testing.T) *transaction.Transaction {
tx := bc.newTestTx(h, orc.GetOracleResponseScript())
resp := &transaction.OracleResponse{
ID: 1,
Code: transaction.Success,
Result: []byte{1, 2, 3},
}
tx.Attributes = []transaction.Attribute{{
Type: transaction.OracleResponseT,
Value: resp,
}}
tx.NetworkFee += 4_000_000 // multisig check
tx.SystemFee = int64(req.GasForResponse - uint64(tx.NetworkFee))
tx.Signers = []transaction.Signer{{
Account: oracleHash,
Scopes: transaction.None,
}}
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), oracleScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
return tx
}
t.Run("NoOracleNodes", func(t *testing.T) {
tx := getOracleTx(t)
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
txSetOracle := transaction.New([]byte{byte(opcode.RET)}, 0) // it's a hack, so we don't need a real script
setSigner(txSetOracle, testchain.CommitteeScriptHash())
txSetOracle.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(txSetOracle),
VerificationScript: testchain.CommitteeVerificationScript(),
}}
bl := block.New(bc.config.StateRootInHeader)
bl.Index = bc.BlockHeight() + 1
ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetOracle)
ic.SpawnVM()
ic.VM.LoadScript([]byte{byte(opcode.RET)})
require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, noderoles.Oracle, oraclePubs))
_, err = ic.DAO.Persist()
require.NoError(t, err)
t.Run("Valid", func(t *testing.T) {
tx := getOracleTx(t)
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
require.NoError(t, bc.VerifyTx(tx))
t.Run("NativeVerify", func(t *testing.T) {
tx.Signers = append(tx.Signers, transaction.Signer{
Account: bc.contracts.Oracle.Hash,
Scopes: transaction.None,
})
tx.Scripts = append(tx.Scripts, transaction.Witness{})
t.Run("NonZeroVerification", func(t *testing.T) {
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Bytes(w.BinWriter, util.Uint160{}.BytesBE())
emit.Int(w.BinWriter, 0)
emit.String(w.BinWriter, orc.Manifest.Name)
tx.Scripts[len(tx.Scripts)-1].VerificationScript = w.Bytes()
err := bc.VerifyTx(tx)
require.True(t, errors.Is(err, ErrNativeContractWitness), "got: %v", err)
})
t.Run("Good", func(t *testing.T) {
tx.Scripts[len(tx.Scripts)-1].VerificationScript = nil
require.NoError(t, bc.VerifyTx(tx))
})
})
})
t.Run("InvalidRequestID", func(t *testing.T) {
tx := getOracleTx(t)
tx.Attributes[0].Value.(*transaction.OracleResponse).ID = 2
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
t.Run("InvalidScope", func(t *testing.T) {
tx := getOracleTx(t)
tx.Signers[0].Scopes = transaction.Global
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
t.Run("InvalidScript", func(t *testing.T) {
tx := getOracleTx(t)
tx.Script = append(tx.Script, byte(opcode.NOP))
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
t.Run("InvalidSigner", func(t *testing.T) {
tx := getOracleTx(t)
tx.Signers[0].Account = accs[0].Contract.ScriptHash()
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
t.Run("SmallFee", func(t *testing.T) {
tx := getOracleTx(t)
tx.SystemFee = 0
require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx))
checkErr(t, ErrInvalidAttribute, tx)
})
})
t.Run("NotValidBefore", func(t *testing.T) {
getNVBTx := func(height uint32) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height}})
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
}}
rawScript := testchain.CommitteeVerificationScript()
require.NoError(t, err)
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: rawScript,
}}
return tx
}
t.Run("Disabled", func(t *testing.T) {
tx := getNVBTx(bc.blockHeight + 1)
require.Error(t, bc.VerifyTx(tx))
})
t.Run("Enabled", func(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("NotYetValid", func(t *testing.T) {
tx := getNVBTx(bc.blockHeight + 1)
require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute))
})
t.Run("positive", func(t *testing.T) {
tx := getNVBTx(bc.blockHeight)
require.NoError(t, bc.VerifyTx(tx))
})
})
})
t.Run("Reserved", func(t *testing.T) {
getReservedTx := func(attrType transaction.AttrType) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: attrType, Value: &transaction.Reserved{Value: []byte{1, 2, 3}}})
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
}}
rawScript := testchain.CommitteeVerificationScript()
require.NoError(t, err)
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: rawScript,
}}
return tx
}
t.Run("Disabled", func(t *testing.T) {
tx := getReservedTx(transaction.ReservedLowerBound + 3)
require.Error(t, bc.VerifyTx(tx))
})
t.Run("Enabled", func(t *testing.T) {
bc.config.ReservedAttributes = true
tx := getReservedTx(transaction.ReservedLowerBound + 3)
require.NoError(t, bc.VerifyTx(tx))
})
})
t.Run("Conflicts", func(t *testing.T) {
getConflictsTx := func(hashes ...util.Uint256) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.Attributes = make([]transaction.Attribute, len(hashes))
for i, h := range hashes {
tx.Attributes[i] = transaction.Attribute{
Type: transaction.ConflictsT,
Value: &transaction.Conflicts{
Hash: h,
},
}
}
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
}}
rawScript := testchain.CommitteeVerificationScript()
require.NoError(t, err)
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: rawScript,
}}
return tx
}
t.Run("disabled", func(t *testing.T) {
bc.config.P2PSigExtensions = false
tx := getConflictsTx(util.Uint256{1, 2, 3})
require.Error(t, bc.VerifyTx(tx))
})
t.Run("enabled", func(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("dummy on-chain conflict", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx))
conflicting := transaction.New([]byte{byte(opcode.RET)}, 1)
conflicting.Attributes = []transaction.Attribute{
{
Type: transaction.ConflictsT,
Value: &transaction.Conflicts{
Hash: tx.Hash(),
},
},
}
require.NoError(t, bc.dao.StoreAsTransaction(conflicting, bc.blockHeight, nil, nil))
require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts))
})
t.Run("attribute on-chain conflict", func(t *testing.T) {
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.ValidUntilBlock = 4242
tx.Signers = []transaction.Signer{{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
}}
require.NoError(t, testchain.SignTx(bc, tx))
b := bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
txConflict := getConflictsTx(tx.Hash())
require.Error(t, bc.VerifyTx(txConflict))
})
t.Run("positive", func(t *testing.T) {
tx := getConflictsTx(random.Uint256())
require.NoError(t, bc.VerifyTx(tx))
})
})
})
t.Run("NotaryAssisted", func(t *testing.T) {
notary, err := wallet.NewAccount()
require.NoError(t, err)
txSetNotary := transaction.New([]byte{byte(opcode.RET)}, 0)
setSigner(txSetNotary, testchain.CommitteeScriptHash())
txSetNotary.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(txSetNotary),
VerificationScript: testchain.CommitteeVerificationScript(),
}}
bl := block.New(false)
bl.Index = bc.BlockHeight() + 1
ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetNotary)
ic.SpawnVM()
ic.VM.LoadScript([]byte{byte(opcode.RET)})
require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, noderoles.P2PNotary, keys.PublicKeys{notary.PrivateKey().PublicKey()}))
_, err = ic.DAO.Persist()
require.NoError(t, err)
getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{
NKeys: signaturesCount,
}})
tx.NetworkFee += serviceFee // additional fee for NotaryAssisted attribute
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
rawScript := testchain.CommitteeVerificationScript()
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: rawScript,
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...),
},
}
return tx
}
t.Run("Disabled", func(t *testing.T) {
bc.config.P2PSigExtensions = false
tx := getNotaryAssistedTx(0, 0)
require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute))
})
t.Run("Enabled, insufficient network fee", func(t *testing.T) {
bc.config.P2PSigExtensions = true
tx := getNotaryAssistedTx(1, 0)
require.Error(t, bc.VerifyTx(tx))
})
t.Run("Test verify", func(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("no NotaryAssisted attribute", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Attributes = []transaction.Attribute{}
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("no deposit", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
}
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...),
},
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary signer scope", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.CalledByEntry,
},
}
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("not signed by Notary", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
}
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary node witness", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
acc, err := keys.NewPrivateKey()
require.NoError(t, err)
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.SignHashable(uint32(testchain.Network()), tx)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("missing payer", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("positive", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
require.NoError(t, bc.VerifyTx(tx))
})
})
})
})
t.Run("Partially-filled transaction", func(t *testing.T) {
bc.config.P2PSigExtensions = true
getPartiallyFilledTx := func(nvb uint32, validUntil uint32) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.ValidUntilBlock = validUntil
tx.Attributes = []transaction.Attribute{
{
Type: transaction.NotValidBeforeT,
Value: &transaction.NotValidBefore{Height: nvb},
},
{
Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: 0},
},
}
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
},
}
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), testchain.MultisigVerificationScript())
tx.NetworkFee = netFee + // multisig witness verification price
int64(size)*bc.FeePerByte() + // fee for unsigned size
int64(sizeDelta)*bc.FeePerByte() + // fee for multisig size
66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script)
2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation)
transaction.NotaryServiceFeePerKey + // fee for Notary attribute
fee.Opcode(bc.GetBaseExecFee(), // Notary verification script
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call
nativeprices.NotaryVerificationPrice*bc.GetBaseExecFee() // Notary witness verification price
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...),
VerificationScript: []byte{},
},
{
InvocationScript: testchain.Sign(tx),
VerificationScript: testchain.MultisigVerificationScript(),
},
}
return tx
}
mp := mempool.New(10, 1, false)
verificationF := func(tx *transaction.Transaction, data interface{}) error {
if data.(int) > 5 {
return errors.New("bad data")
}
return nil
}
t.Run("failed pre-verification", func(t *testing.T) {
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
require.Error(t, bc.PoolTxWithData(tx, 6, mp, bc, verificationF)) // here and below let's use `bc` instead of proper NotaryFeer for the test simplicity.
})
t.Run("GasLimitExceeded during witness verification", func(t *testing.T) {
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
tx.NetworkFee-- // to check that NetworkFee was set correctly in getPartiallyFilledTx
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...),
VerificationScript: []byte{},
},
{
InvocationScript: testchain.Sign(tx),
VerificationScript: testchain.MultisigVerificationScript(),
},
}
require.Error(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF))
})
t.Run("bad NVB: too big", func(t *testing.T) {
tx := getPartiallyFilledTx(bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1, bc.blockHeight+1)
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute))
})
t.Run("bad ValidUntilBlock: too small", func(t *testing.T) {
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1)
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute))
})
t.Run("good", func(t *testing.T) {
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
require.NoError(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF))
})
})
}
func TestVerifyHashAgainstScript(t *testing.T) {
bc := newTestChain(t)
cs, csInvalid := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid))
gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO)
t.Run("Contract", func(t *testing.T) {
t.Run("Missing", func(t *testing.T) {
newH := cs.Hash
newH[0] = ^newH[0]
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
_, 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.Hash, 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.Hash, 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.Hash, w, ic, 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, 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, gas)
require.True(t, errors.Is(err, ErrVerificationFailed))
})
t.Run("BadResult", func(t *testing.T) {
verif := make([]byte, 66)
verif[0] = byte(opcode.PUSHDATA1)
verif[1] = 64
w := &transaction.Witness{
InvocationScript: []byte{byte(opcode.NOP)},
VerificationScript: verif,
}
_, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas)
require.True(t, errors.Is(err, ErrVerificationFailed))
})
t.Run("TooManyResults", func(t *testing.T) {
verif := []byte{byte(opcode.NOP)}
w := &transaction.Witness{
InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.PUSH1)},
VerificationScript: verif,
}
_, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas)
require.True(t, errors.Is(err, ErrVerificationFailed))
})
}
func TestIsTxStillRelevant(t *testing.T) {
bc := newTestChain(t)
mp := bc.GetMemPool()
newTx := func(t *testing.T) *transaction.Transaction {
tx := transaction.New([]byte{byte(opcode.RET)}, 100)
tx.ValidUntilBlock = bc.BlockHeight() + 1
tx.Signers = []transaction.Signer{{
Account: neoOwner,
Scopes: transaction.CalledByEntry,
}}
return tx
}
t.Run("small ValidUntilBlock", func(t *testing.T) {
tx := newTx(t)
require.NoError(t, testchain.SignTx(bc, tx))
require.True(t, bc.IsTxStillRelevant(tx, nil, false))
require.NoError(t, bc.AddBlock(bc.newBlock()))
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
})
t.Run("tx is already persisted", func(t *testing.T) {
tx := newTx(t)
tx.ValidUntilBlock = bc.BlockHeight() + 2
require.NoError(t, testchain.SignTx(bc, tx))
require.True(t, bc.IsTxStillRelevant(tx, nil, false))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
})
t.Run("tx with Conflicts attribute", func(t *testing.T) {
tx1 := newTx(t)
require.NoError(t, testchain.SignTx(bc, tx1))
tx2 := newTx(t)
tx2.Attributes = []transaction.Attribute{{
Type: transaction.ConflictsT,
Value: &transaction.Conflicts{Hash: tx1.Hash()},
}}
require.NoError(t, testchain.SignTx(bc, tx2))
require.True(t, bc.IsTxStillRelevant(tx1, mp, false))
require.NoError(t, bc.verifyAndPoolTx(tx2, mp, bc))
require.False(t, bc.IsTxStillRelevant(tx1, mp, false))
})
t.Run("NotValidBefore", func(t *testing.T) {
tx3 := newTx(t)
tx3.Attributes = []transaction.Attribute{{
Type: transaction.NotValidBeforeT,
Value: &transaction.NotValidBefore{Height: bc.BlockHeight() + 1},
}}
tx3.ValidUntilBlock = bc.BlockHeight() + 2
require.NoError(t, testchain.SignTx(bc, tx3))
require.False(t, bc.IsTxStillRelevant(tx3, nil, false))
require.NoError(t, bc.AddBlock(bc.newBlock()))
require.True(t, bc.IsTxStillRelevant(tx3, nil, false))
})
t.Run("contract witness check fails", func(t *testing.T) {
src := fmt.Sprintf(`package verify
import (
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
)
func Verify() bool {
addr := util.FromAddress("`+address.Uint160ToString(bc.contracts.Ledger.Hash)+`")
currentHeight := contract.Call(addr, "currentIndex", contract.ReadStates)
return currentHeight.(int) < %d
}`, bc.BlockHeight()+2) // deploy + next block
txDeploy, h, _, err := testchain.NewDeployTx(bc, "TestVerify", neoOwner, strings.NewReader(src), nil)
require.NoError(t, err)
txDeploy.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(neoOwner, txDeploy)
require.NoError(t, testchain.SignTx(bc, txDeploy))
require.NoError(t, bc.AddBlock(bc.newBlock(txDeploy)))
tx := newTx(t)
tx.Signers = append(tx.Signers, transaction.Signer{
Account: h,
Scopes: transaction.None,
})
tx.NetworkFee += 10_000_000
require.NoError(t, testchain.SignTx(bc, tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{})
require.True(t, bc.IsTxStillRelevant(tx, mp, false))
require.NoError(t, bc.AddBlock(bc.newBlock()))
require.False(t, bc.IsTxStillRelevant(tx, mp, false))
})
}
func TestMemPoolRemoval(t *testing.T) {
const added = 16
const notAdded = 32
bc := newTestChain(t)
addedTxes := make([]*transaction.Transaction, added)
notAddedTxes := make([]*transaction.Transaction, notAdded)
for i := range addedTxes {
addedTxes[i] = bc.newTestTx(testchain.MultisigScriptHash(), []byte{byte(opcode.PUSH1)})
require.NoError(t, testchain.SignTx(bc, addedTxes[i]))
require.NoError(t, bc.PoolTx(addedTxes[i]))
}
for i := range notAddedTxes {
notAddedTxes[i] = bc.newTestTx(testchain.MultisigScriptHash(), []byte{byte(opcode.PUSH1)})
require.NoError(t, testchain.SignTx(bc, notAddedTxes[i]))
require.NoError(t, bc.PoolTx(notAddedTxes[i]))
}
b := bc.newBlock(addedTxes...)
require.NoError(t, bc.AddBlock(b))
mempool := bc.GetMemPool()
for _, tx := range addedTxes {
require.False(t, mempool.ContainsKey(tx.Hash()))
}
for _, tx := range notAddedTxes {
require.True(t, mempool.ContainsKey(tx.Hash()))
}
}
func TestHasBlock(t *testing.T) {
bc := newTestChain(t)
blocks, err := bc.genBlocks(50)
require.NoError(t, err)
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
for i := 0; i < len(blocks); i++ {
assert.True(t, bc.HasBlock(blocks[i].Hash()))
}
newBlock := bc.newBlock()
assert.False(t, bc.HasBlock(newBlock.Hash()))
_, err = bc.persist(true)
assert.NoError(t, err)
}
}
func TestGetTransaction(t *testing.T) {
bc := newTestChain(t)
tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx1.ValidUntilBlock = 16
tx1.Signers = []transaction.Signer{{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.CalledByEntry,
}}
tx2 := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
tx2.ValidUntilBlock = 16
tx2.Signers = []transaction.Signer{{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.CalledByEntry,
}}
require.NoError(t, testchain.SignTx(bc, tx1, tx2))
b1 := bc.newBlock(tx1)
assert.Nil(t, bc.AddBlock(b1))
block := bc.newBlock(tx2)
txSize := io.GetVarSize(tx2)
assert.Nil(t, bc.AddBlock(block))
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
tx, height, err := bc.GetTransaction(block.Transactions[0].Hash())
require.Nil(t, err)
assert.Equal(t, block.Index, height)
assert.Equal(t, txSize, tx.Size())
assert.Equal(t, block.Transactions[0], tx)
assert.Equal(t, 1, io.GetVarSize(tx.Attributes))
assert.Equal(t, 1, io.GetVarSize(tx.Scripts))
_, err = bc.persist(true)
assert.NoError(t, err)
}
}
func TestGetClaimable(t *testing.T) {
bc := newTestChain(t)
_, err := bc.genBlocks(10)
require.NoError(t, err)
t.Run("first generation period", func(t *testing.T) {
amount, err := bc.CalculateClaimable(neoOwner, 1)
require.NoError(t, err)
require.EqualValues(t, big.NewInt(5*native.GASFactor/10), amount)
})
}
func TestClose(t *testing.T) {
defer func() {
r := recover()
assert.NotNil(t, r)
}()
bc := initTestChain(t, nil, nil)
go bc.Run()
_, err := bc.genBlocks(10)
require.NoError(t, err)
bc.Close()
// It's a hack, but we use internal knowledge of MemoryStore
// implementation which makes it completely unusable (up to panicing)
// after Close().
_ = bc.dao.Store.Put([]byte{0}, []byte{1})
// This should never be executed.
assert.Nil(t, t)
}
func TestSubscriptions(t *testing.T) {
// We use buffering here as a substitute for reader goroutines, events
// get queued up and we read them one by one here.
const chBufSize = 16
blockCh := make(chan *block.Block, chBufSize)
txCh := make(chan *transaction.Transaction, chBufSize)
notificationCh := make(chan *subscriptions.NotificationEvent, chBufSize)
executionCh := make(chan *state.AppExecResult, chBufSize)
bc := newTestChain(t)
bc.SubscribeForBlocks(blockCh)
bc.SubscribeForTransactions(txCh)
bc.SubscribeForNotifications(notificationCh)
bc.SubscribeForExecutions(executionCh)
assert.Empty(t, notificationCh)
assert.Empty(t, executionCh)
assert.Empty(t, blockCh)
assert.Empty(t, txCh)
blocks, err := bc.genBlocks(1)
require.NoError(t, err)
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
assert.Len(t, notificationCh, 1) // validator bounty
assert.Len(t, executionCh, 2)
assert.Empty(t, txCh)
b := <-blockCh
assert.Equal(t, blocks[0], b)
assert.Empty(t, blockCh)
aer := <-executionCh
assert.Equal(t, b.Hash(), aer.Container)
aer = <-executionCh
assert.Equal(t, b.Hash(), aer.Container)
notif := <-notificationCh
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("yay!"))
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify)
require.NoError(t, script.Err)
txGood1 := transaction.New(script.Bytes(), 0)
txGood1.Signers = []transaction.Signer{{Account: neoOwner}}
txGood1.Nonce = 1
txGood1.ValidUntilBlock = 1024
require.NoError(t, testchain.SignTx(bc, txGood1))
// Reset() reuses the script buffer and we need to keep scripts.
script = io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("nay!"))
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(script.BinWriter, opcode.THROW)
require.NoError(t, script.Err)
txBad := transaction.New(script.Bytes(), 0)
txBad.Signers = []transaction.Signer{{Account: neoOwner}}
txBad.Nonce = 2
txBad.ValidUntilBlock = 1024
require.NoError(t, testchain.SignTx(bc, txBad))
script = io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("yay! yay! yay!"))
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify)
require.NoError(t, script.Err)
txGood2 := transaction.New(script.Bytes(), 0)
txGood2.Signers = []transaction.Signer{{Account: neoOwner}}
txGood2.Nonce = 3
txGood2.ValidUntilBlock = 1024
require.NoError(t, testchain.SignTx(bc, txGood2))
invBlock := newBlock(bc.config, bc.BlockHeight()+1, bc.CurrentHeaderHash(), txGood1, txBad, txGood2)
require.NoError(t, bc.AddBlock(invBlock))
require.Eventually(t, func() bool {
return len(blockCh) != 0 && len(txCh) != 0 &&
len(notificationCh) != 0 && len(executionCh) != 0
}, time.Second, 10*time.Millisecond)
b = <-blockCh
require.Equal(t, invBlock, b)
assert.Empty(t, blockCh)
exec := <-executionCh
require.Equal(t, b.Hash(), exec.Container)
require.Equal(t, exec.VMState, vm.HaltState)
// 3 burn events for every tx and 1 mint for primary node
require.True(t, len(notificationCh) >= 4)
for i := 0; i < 4; i++ {
notif := <-notificationCh
require.Equal(t, bc.contracts.GAS.Hash, notif.ScriptHash)
}
// Follow in-block transaction order.
for _, txExpected := range invBlock.Transactions {
tx := <-txCh
require.Equal(t, txExpected, tx)
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.Container)
if exec.VMState == vm.HaltState {
notif := <-notificationCh
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
}
}
assert.Empty(t, txCh)
assert.Len(t, notificationCh, 1)
assert.Len(t, executionCh, 1)
notif = <-notificationCh
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
exec = <-executionCh
require.Equal(t, b.Hash(), exec.Container)
require.Equal(t, exec.VMState, vm.HaltState)
bc.UnsubscribeFromBlocks(blockCh)
bc.UnsubscribeFromTransactions(txCh)
bc.UnsubscribeFromNotifications(notificationCh)
bc.UnsubscribeFromExecutions(executionCh)
// Ensure that new blocks are processed correctly after unsubscription.
_, err = bc.genBlocks(2 * chBufSize)
require.NoError(t, err)
}
func testDumpAndRestore(t *testing.T, dumpF, restoreF func(c *config.Config)) {
if restoreF == nil {
restoreF = dumpF
}
bc := newTestChainWithCustomCfg(t, dumpF)
initBasicChain(t, bc)
require.True(t, bc.BlockHeight() > 5) // ensure that test is valid
w := io.NewBufBinWriter()
require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1))
require.NoError(t, w.Err)
buf := w.Bytes()
t.Run("invalid start", func(t *testing.T) {
bc2 := newTestChainWithCustomCfg(t, restoreF)
r := io.NewBinReaderFromBuf(buf)
require.Error(t, chaindump.Restore(bc2, r, 2, 1, nil))
})
t.Run("good", func(t *testing.T) {
bc2 := newTestChainWithCustomCfg(t, restoreF)
r := io.NewBinReaderFromBuf(buf)
require.NoError(t, chaindump.Restore(bc2, r, 0, 2, nil))
require.Equal(t, uint32(1), bc2.BlockHeight())
r = io.NewBinReaderFromBuf(buf) // new reader because start is relative to dump
require.NoError(t, chaindump.Restore(bc2, r, 2, 1, nil))
t.Run("check handler", func(t *testing.T) {
lastIndex := uint32(0)
errStopped := errors.New("stopped")
f := func(b *block.Block) error {
lastIndex = b.Index
if b.Index >= bc.BlockHeight()-1 {
return errStopped
}
return nil
}
require.NoError(t, chaindump.Restore(bc2, r, 0, 1, f))
require.Equal(t, bc2.BlockHeight(), lastIndex)
r = io.NewBinReaderFromBuf(buf)
err := chaindump.Restore(bc2, r, 4, bc.BlockHeight()-bc2.BlockHeight(), f)
require.True(t, errors.Is(err, errStopped))
require.Equal(t, bc.BlockHeight()-1, lastIndex)
})
})
}
func TestDumpAndRestore(t *testing.T) {
t.Run("no state root", func(t *testing.T) {
testDumpAndRestore(t, func(c *config.Config) {
c.ProtocolConfiguration.StateRootInHeader = false
}, nil)
})
t.Run("with state root", func(t *testing.T) {
testDumpAndRestore(t, func(c *config.Config) {
c.ProtocolConfiguration.StateRootInHeader = false
}, nil)
})
t.Run("remove untraceable", func(t *testing.T) {
// Dump can only be created if all blocks and transactions are present.
testDumpAndRestore(t, nil, func(c *config.Config) {
c.ProtocolConfiguration.MaxTraceableBlocks = 2
c.ProtocolConfiguration.RemoveUntraceableBlocks = true
})
})
}
func TestRemoveUntraceable(t *testing.T) {
check := func(t *testing.T, bc *Blockchain, tHash, bHash util.Uint256, errorExpected bool) {
_, _, err := bc.GetTransaction(tHash)
if errorExpected {
require.Error(t, err)
} else {
require.NoError(t, err)
}
_, err = bc.GetAppExecResults(tHash, trigger.Application)
if errorExpected {
require.Error(t, err)
} else {
require.NoError(t, err)
}
_, err = bc.GetBlock(bHash)
if errorExpected {
require.Error(t, err)
} else {
require.NoError(t, err)
}
_, err = bc.GetHeader(bHash)
require.NoError(t, err)
}
t.Run("P2PStateExchangeExtensions off", func(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.MaxTraceableBlocks = 2
c.ProtocolConfiguration.RemoveUntraceableBlocks = true
})
tx1, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1)
require.NoError(t, err)
b1 := bc.newBlock(tx1)
require.NoError(t, bc.AddBlock(b1))
tx1Height := bc.BlockHeight()
tx2, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1)
require.NoError(t, err)
require.NoError(t, bc.AddBlock(bc.newBlock(tx2)))
_, h1, err := bc.GetTransaction(tx1.Hash())
require.NoError(t, err)
require.Equal(t, tx1Height, h1)
require.NoError(t, bc.AddBlock(bc.newBlock()))
check(t, bc, tx1.Hash(), b1.Hash(), true)
})
t.Run("P2PStateExchangeExtensions on", func(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.MaxTraceableBlocks = 2
c.ProtocolConfiguration.RemoveUntraceableBlocks = true
c.ProtocolConfiguration.P2PStateExchangeExtensions = true
c.ProtocolConfiguration.StateSyncInterval = 2
c.ProtocolConfiguration.StateRootInHeader = true
})
tx1, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1)
require.NoError(t, err)
b1 := bc.newBlock(tx1)
require.NoError(t, bc.AddBlock(b1))
tx1Height := bc.BlockHeight()
tx2, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1)
require.NoError(t, err)
b2 := bc.newBlock(tx2)
require.NoError(t, bc.AddBlock(b2))
tx2Height := bc.BlockHeight()
_, h1, err := bc.GetTransaction(tx1.Hash())
require.NoError(t, err)
require.Equal(t, tx1Height, h1)
require.NoError(t, bc.AddBlock(bc.newBlock()))
require.NoError(t, bc.AddBlock(bc.newBlock()))
require.NoError(t, bc.AddBlock(bc.newBlock()))
check(t, bc, tx1.Hash(), b1.Hash(), false)
check(t, bc, tx2.Hash(), b2.Hash(), false)
require.NoError(t, bc.AddBlock(bc.newBlock()))
check(t, bc, tx1.Hash(), b1.Hash(), true)
check(t, bc, tx2.Hash(), b2.Hash(), false)
_, h2, err := bc.GetTransaction(tx2.Hash())
require.NoError(t, err)
require.Equal(t, tx2Height, h2)
})
}
func TestInvalidNotification(t *testing.T) {
bc := newTestChain(t)
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack")
require.NoError(t, err)
require.Equal(t, 2, len(aer.Stack))
require.Nil(t, aer.Stack[0])
require.Equal(t, stackitem.InteropT, aer.Stack[1].Type())
}
// Test that deletion of non-existent doesn't result in error in tx or block addition.
func TestMPTDeleteNoKey(t *testing.T) {
bc := newTestChain(t)
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key")
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer.VMState)
}
// Test that UpdateHistory is added to ProtocolConfiguration for all native contracts
// for all default configurations. If UpdateHistory is not added to config, then
// native contract is disabled. It's easy to forget about config while adding new
// native contract.
func TestConfigNativeUpdateHistory(t *testing.T) {
var prefixPath = filepath.Join("..", "..", "config")
check := func(t *testing.T, cfgFileSuffix interface{}) {
cfgPath := filepath.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix))
cfg, err := config.LoadFile(cfgPath)
require.NoError(t, err, fmt.Errorf("failed to load %s", cfgPath))
natives := native.NewContracts(cfg.ProtocolConfiguration)
assert.Equal(t, len(natives.Contracts),
len(cfg.ProtocolConfiguration.NativeUpdateHistories),
fmt.Errorf("protocol configuration file %s: extra or missing NativeUpdateHistory in NativeActivations section", cfgPath))
for _, c := range natives.Contracts {
assert.NotNil(t, cfg.ProtocolConfiguration.NativeUpdateHistories[c.Metadata().Name],
fmt.Errorf("protocol configuration file %s: configuration for %s native contract is missing in NativeActivations section; "+
"edit the test if the contract should be disabled", cfgPath, c.Metadata().Name))
}
}
testCases := []interface{}{
netmode.MainNet,
netmode.PrivNet,
netmode.TestNet,
netmode.UnitTestNet,
"privnet.docker.one",
"privnet.docker.two",
"privnet.docker.three",
"privnet.docker.four",
"privnet.docker.single",
"unit_testnet.single",
}
for _, tc := range testCases {
check(t, tc)
}
}
func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
var (
stateSyncInterval = 4
maxTraceable uint32 = 6
)
spountCfg := func(c *config.Config) {
c.ProtocolConfiguration.RemoveUntraceableBlocks = true
c.ProtocolConfiguration.StateRootInHeader = true
c.ProtocolConfiguration.P2PStateExchangeExtensions = true
c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval
c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable
}
bcSpout := newTestChainWithCustomCfg(t, spountCfg)
initBasicChain(t, bcSpout)
// reach next to the latest state sync point and pretend that we've just restored
stateSyncPoint := (int(bcSpout.BlockHeight())/stateSyncInterval + 1) * stateSyncInterval
for i := bcSpout.BlockHeight() + 1; i <= uint32(stateSyncPoint); i++ {
require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock()))
}
require.Equal(t, uint32(stateSyncPoint), bcSpout.BlockHeight())
b := bcSpout.newBlock()
require.NoError(t, bcSpout.AddHeaders(&b.Header))
// put storage items with STTemp prefix
batch := bcSpout.dao.Store.Batch()
tempPrefix := storage.STTempStorage
if bcSpout.dao.Version.StoragePrefix == tempPrefix {
tempPrefix = storage.STStorage
}
bcSpout.dao.Store.Seek(storage.SeekRange{Prefix: bcSpout.dao.Version.StoragePrefix.Bytes()}, func(k, v []byte) bool {
key := slice.Copy(k)
key[0] = byte(tempPrefix)
value := slice.Copy(v)
batch.Put(key, value)
return true
})
require.NoError(t, bcSpout.dao.Store.PutBatch(batch))
checkNewBlockchainErr := func(t *testing.T, cfg func(c *config.Config), store storage.Store, shouldFail bool) {
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err)
cfg(&unitTestNetCfg)
log := zaptest.NewLogger(t)
_, err = NewBlockchain(store, unitTestNetCfg.ProtocolConfiguration, log)
if shouldFail {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
boltCfg := func(c *config.Config) {
spountCfg(c)
c.ProtocolConfiguration.KeepOnlyLatestState = true
}
// manually store statejump stage to check statejump recover process
t.Run("invalid RemoveUntraceableBlocks setting", func(t *testing.T) {
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateJumpStage.Bytes(), []byte{byte(stateJumpStarted)}))
checkNewBlockchainErr(t, func(c *config.Config) {
boltCfg(c)
c.ProtocolConfiguration.RemoveUntraceableBlocks = false
}, bcSpout.dao.Store, true)
})
t.Run("invalid state jump stage format", func(t *testing.T) {
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateJumpStage.Bytes(), []byte{0x01, 0x02}))
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, true)
})
t.Run("missing state sync point", func(t *testing.T) {
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateJumpStage.Bytes(), []byte{byte(stateJumpStarted)}))
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, true)
})
t.Run("invalid state sync point", func(t *testing.T) {
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateJumpStage.Bytes(), []byte{byte(stateJumpStarted)}))
point := make([]byte, 4)
binary.LittleEndian.PutUint32(point, uint32(len(bcSpout.headerHashes)))
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateSyncPoint.Bytes(), point))
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, true)
})
for _, stage := range []stateJumpStage{stateJumpStarted, newStorageItemsAdded, genesisStateRemoved, 0x03} {
t.Run(fmt.Sprintf("state jump stage %d", stage), func(t *testing.T) {
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateJumpStage.Bytes(), []byte{byte(stage)}))
point := make([]byte, 4)
binary.LittleEndian.PutUint32(point, uint32(stateSyncPoint))
require.NoError(t, bcSpout.dao.Store.Put(storage.SYSStateSyncPoint.Bytes(), point))
shouldFail := stage == 0x03 // unknown stage
checkNewBlockchainErr(t, spountCfg, bcSpout.dao.Store, shouldFail)
})
}
}
func setSigner(tx *transaction.Transaction, h util.Uint160) {
tx.Signers = []transaction.Signer{{
Account: h,
Scopes: transaction.Global,
}}
}