github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/neotest/chain/chain.go (about)

     1  package chain
     2  
     3  import (
     4  	"encoding/hex"
     5  	"sort"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/config"
    10  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    11  	"github.com/nspcc-dev/neo-go/pkg/core"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
    13  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    14  	"github.com/nspcc-dev/neo-go/pkg/neotest"
    15  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    16  	"github.com/nspcc-dev/neo-go/pkg/wallet"
    17  	"github.com/stretchr/testify/require"
    18  	"go.uber.org/zap"
    19  	"go.uber.org/zap/zaptest"
    20  )
    21  
    22  const (
    23  	// MaxTraceableBlocks is the default MaxTraceableBlocks setting used for test chains.
    24  	// We don't need a lot of traceable blocks for tests.
    25  	MaxTraceableBlocks = 1000
    26  
    27  	// TimePerBlock is the default TimePerBlock setting used for test chains (1s).
    28  	// Usually blocks are created by tests bypassing this setting.
    29  	TimePerBlock = time.Second
    30  )
    31  
    32  const singleValidatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY"
    33  
    34  // committeeWIFs is a list of unencrypted WIFs sorted by the public key.
    35  var committeeWIFs = []string{
    36  	"KzfPUYDC9n2yf4fK5ro4C8KMcdeXtFuEnStycbZgX3GomiUsvX6W",
    37  	"KzgWE3u3EDp13XPXXuTKZxeJ3Gi8Bsm8f9ijY3ZsCKKRvZUo1Cdn",
    38  	singleValidatorWIF,
    39  	"L2oEXKRAAMiPEZukwR5ho2S6SMeQLhcK9mF71ZnF7GvT8dU4Kkgz",
    40  
    41  	// Provide 2 committee extra members so that the committee address differs from
    42  	// the validators one.
    43  	"L1Tr1iq5oz1jaFaMXP21sHDkJYDDkuLtpvQ4wRf1cjKvJYvnvpAb",
    44  	"Kz6XTUrExy78q8f4MjDHnwz8fYYyUE8iPXwPRAkHa3qN2JcHYm7e",
    45  }
    46  
    47  var (
    48  	// committeeAcc is an account used to sign a tx as a committee.
    49  	committeeAcc *wallet.Account
    50  
    51  	// multiCommitteeAcc contains committee accounts used in a multi-node setup.
    52  	multiCommitteeAcc []*wallet.Account
    53  
    54  	// multiValidatorAcc contains validator accounts used in a multi-node setup.
    55  	multiValidatorAcc []*wallet.Account
    56  
    57  	// standByCommittee contains a list of committee public keys to use in config.
    58  	standByCommittee []string
    59  )
    60  
    61  // Options contains parameters to customize parameters of the test chain.
    62  type Options struct {
    63  	// Logger allows to customize logging performed by the test chain.
    64  	// If Logger is not set, zaptest.Logger will be used with default configuration.
    65  	Logger *zap.Logger
    66  	// BlockchainConfigHook function is sort of visitor pattern for blockchain configuration.
    67  	// It takes in the default configuration as an argument and can perform any adjustments in it.
    68  	BlockchainConfigHook func(*config.Blockchain)
    69  	// Store allows to customize storage for blockchain data.
    70  	// If Store is not set, MemoryStore is used by default.
    71  	Store storage.Store
    72  	// If SkipRun is false, then the blockchain will be started (if its' construction
    73  	// has succeeded) and will be registered for cleanup when the test completes.
    74  	// If SkipRun is true, it is caller's responsibility to call Run before using
    75  	// the chain and to properly Close the chain when done.
    76  	SkipRun bool
    77  }
    78  
    79  func init() {
    80  	committeeAcc, _ = wallet.NewAccountFromWIF(singleValidatorWIF)
    81  	pubs := keys.PublicKeys{committeeAcc.PublicKey()}
    82  	err := committeeAcc.ConvertMultisig(1, pubs)
    83  	if err != nil {
    84  		panic(err)
    85  	}
    86  
    87  	mc := smartcontract.GetMajorityHonestNodeCount(len(committeeWIFs))
    88  	mv := smartcontract.GetDefaultHonestNodeCount(4)
    89  	accs := make([]*wallet.Account, len(committeeWIFs))
    90  	pubs = make(keys.PublicKeys, len(accs))
    91  	for i := range committeeWIFs {
    92  		accs[i], _ = wallet.NewAccountFromWIF(committeeWIFs[i])
    93  		pubs[i] = accs[i].PublicKey()
    94  	}
    95  
    96  	// Config entry must contain validators first in a specific order.
    97  	standByCommittee = make([]string, len(pubs))
    98  	standByCommittee[0] = pubs[2].StringCompressed()
    99  	standByCommittee[1] = pubs[0].StringCompressed()
   100  	standByCommittee[2] = pubs[3].StringCompressed()
   101  	standByCommittee[3] = pubs[1].StringCompressed()
   102  	standByCommittee[4] = pubs[4].StringCompressed()
   103  	standByCommittee[5] = pubs[5].StringCompressed()
   104  
   105  	multiValidatorAcc = make([]*wallet.Account, 4)
   106  	sort.Sort(pubs[:4])
   107  
   108  	sort.Slice(accs[:4], func(i, j int) bool {
   109  		p1 := accs[i].PublicKey()
   110  		p2 := accs[j].PublicKey()
   111  		return p1.Cmp(p2) == -1
   112  	})
   113  	for i := range multiValidatorAcc {
   114  		multiValidatorAcc[i] = wallet.NewAccountFromPrivateKey(accs[i].PrivateKey())
   115  		err := multiValidatorAcc[i].ConvertMultisig(mv, pubs[:4])
   116  		if err != nil {
   117  			panic(err)
   118  		}
   119  	}
   120  
   121  	multiCommitteeAcc = make([]*wallet.Account, len(committeeWIFs))
   122  	sort.Sort(pubs)
   123  
   124  	sort.Slice(accs, func(i, j int) bool {
   125  		p1 := accs[i].PublicKey()
   126  		p2 := accs[j].PublicKey()
   127  		return p1.Cmp(p2) == -1
   128  	})
   129  	for i := range multiCommitteeAcc {
   130  		multiCommitteeAcc[i] = wallet.NewAccountFromPrivateKey(accs[i].PrivateKey())
   131  		err := multiCommitteeAcc[i].ConvertMultisig(mc, pubs)
   132  		if err != nil {
   133  			panic(err)
   134  		}
   135  	}
   136  }
   137  
   138  // NewSingle creates a new blockchain instance with a single validator and
   139  // setups cleanup functions. The configuration used is with netmode.UnitTestNet
   140  // magic and TimePerBlock/MaxTraceableBlocks options defined by constants in
   141  // this package. MemoryStore is used as the backend storage, so all of the chain
   142  // contents is always in RAM. The Signer returned is the validator (and the committee at
   143  // the same time).
   144  func NewSingle(t testing.TB) (*core.Blockchain, neotest.Signer) {
   145  	return NewSingleWithCustomConfig(t, nil)
   146  }
   147  
   148  // NewSingleWithCustomConfig is similar to NewSingle, but allows to override the
   149  // default configuration.
   150  func NewSingleWithCustomConfig(t testing.TB, f func(*config.Blockchain)) (*core.Blockchain, neotest.Signer) {
   151  	return NewSingleWithCustomConfigAndStore(t, f, nil, true)
   152  }
   153  
   154  // NewSingleWithCustomConfigAndStore is similar to NewSingleWithCustomConfig, but
   155  // also allows to override backend Store being used. The last parameter controls if
   156  // Run method is called on the Blockchain instance. If not, it is its caller's
   157  // responsibility to do that before using the chain and
   158  // to properly Close the chain when done.
   159  func NewSingleWithCustomConfigAndStore(t testing.TB, f func(cfg *config.Blockchain), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) {
   160  	return NewSingleWithOptions(t, &Options{
   161  		BlockchainConfigHook: f,
   162  		Store:                st,
   163  		SkipRun:              !run,
   164  	})
   165  }
   166  
   167  // NewSingleWithOptions creates a new blockchain instance with a single validator
   168  // using specified options.
   169  func NewSingleWithOptions(t testing.TB, options *Options) (*core.Blockchain, neotest.Signer) {
   170  	if options == nil {
   171  		options = &Options{}
   172  	}
   173  
   174  	cfg := config.Blockchain{
   175  		ProtocolConfiguration: config.ProtocolConfiguration{
   176  			Magic:              netmode.UnitTestNet,
   177  			MaxTraceableBlocks: MaxTraceableBlocks,
   178  			TimePerBlock:       TimePerBlock,
   179  			StandbyCommittee:   []string{hex.EncodeToString(committeeAcc.PublicKey().Bytes())},
   180  			ValidatorsCount:    1,
   181  			VerifyTransactions: true,
   182  		},
   183  	}
   184  	if options.BlockchainConfigHook != nil {
   185  		options.BlockchainConfigHook(&cfg)
   186  	}
   187  
   188  	store := options.Store
   189  	if store == nil {
   190  		store = storage.NewMemoryStore()
   191  	}
   192  
   193  	logger := options.Logger
   194  	if logger == nil {
   195  		logger = zaptest.NewLogger(t)
   196  	}
   197  
   198  	bc, err := core.NewBlockchain(store, cfg, logger)
   199  	require.NoError(t, err)
   200  	if !options.SkipRun {
   201  		go bc.Run()
   202  		t.Cleanup(bc.Close)
   203  	}
   204  	return bc, neotest.NewMultiSigner(committeeAcc)
   205  }
   206  
   207  // NewMulti creates a new blockchain instance with four validators and six
   208  // committee members. Otherwise, it does not differ much from NewSingle. The
   209  // second value returned contains the validators Signer, the third -- the committee one.
   210  func NewMulti(t testing.TB) (*core.Blockchain, neotest.Signer, neotest.Signer) {
   211  	return NewMultiWithCustomConfig(t, nil)
   212  }
   213  
   214  // NewMultiWithCustomConfig is similar to NewMulti, except it allows to override the
   215  // default configuration.
   216  func NewMultiWithCustomConfig(t testing.TB, f func(*config.Blockchain)) (*core.Blockchain, neotest.Signer, neotest.Signer) {
   217  	return NewMultiWithCustomConfigAndStore(t, f, nil, true)
   218  }
   219  
   220  // NewMultiWithCustomConfigAndStore is similar to NewMultiWithCustomConfig, but
   221  // also allows to override backend Store being used. The last parameter controls if
   222  // Run method is called on the Blockchain instance. If not, it is its caller's
   223  // responsibility to do that before using the chain and
   224  // to properly Close the chain when done.
   225  func NewMultiWithCustomConfigAndStore(t testing.TB, f func(*config.Blockchain), st storage.Store, run bool) (*core.Blockchain, neotest.Signer, neotest.Signer) {
   226  	bc, validator, committee, err := NewMultiWithCustomConfigAndStoreNoCheck(t, f, st)
   227  	require.NoError(t, err)
   228  	if run {
   229  		go bc.Run()
   230  		t.Cleanup(bc.Close)
   231  	}
   232  	return bc, validator, committee
   233  }
   234  
   235  // NewMultiWithOptions creates a new blockchain instance with four validators and six
   236  // committee members. Otherwise, it does not differ much from NewSingle. The
   237  // second value returned contains the validators Signer, the third -- the committee one.
   238  func NewMultiWithOptions(t testing.TB, options *Options) (*core.Blockchain, neotest.Signer, neotest.Signer) {
   239  	bc, validator, committee, err := NewMultiWithOptionsNoCheck(t, options)
   240  	require.NoError(t, err)
   241  	return bc, validator, committee
   242  }
   243  
   244  // NewMultiWithCustomConfigAndStoreNoCheck is similar to NewMultiWithCustomConfig,
   245  // but do not perform Blockchain run and do not check Blockchain constructor error.
   246  func NewMultiWithCustomConfigAndStoreNoCheck(t testing.TB, f func(*config.Blockchain), st storage.Store) (*core.Blockchain, neotest.Signer, neotest.Signer, error) {
   247  	return NewMultiWithOptionsNoCheck(t, &Options{
   248  		BlockchainConfigHook: f,
   249  		Store:                st,
   250  		SkipRun:              true,
   251  	})
   252  }
   253  
   254  // NewMultiWithOptionsNoCheck is similar to NewMultiWithOptions, but does not verify blockchain constructor error.
   255  // It will start blockchain only if construction has completed successfully.
   256  func NewMultiWithOptionsNoCheck(t testing.TB, options *Options) (*core.Blockchain, neotest.Signer, neotest.Signer, error) {
   257  	if options == nil {
   258  		options = &Options{}
   259  	}
   260  
   261  	cfg := config.Blockchain{
   262  		ProtocolConfiguration: config.ProtocolConfiguration{
   263  			Magic:              netmode.UnitTestNet,
   264  			MaxTraceableBlocks: MaxTraceableBlocks,
   265  			TimePerBlock:       TimePerBlock,
   266  			StandbyCommittee:   standByCommittee,
   267  			ValidatorsCount:    4,
   268  			VerifyTransactions: true,
   269  		},
   270  	}
   271  	if options.BlockchainConfigHook != nil {
   272  		options.BlockchainConfigHook(&cfg)
   273  	}
   274  
   275  	store := options.Store
   276  	if store == nil {
   277  		store = storage.NewMemoryStore()
   278  	}
   279  
   280  	logger := options.Logger
   281  	if logger == nil {
   282  		logger = zaptest.NewLogger(t)
   283  	}
   284  
   285  	bc, err := core.NewBlockchain(store, cfg, logger)
   286  	if err == nil && !options.SkipRun {
   287  		go bc.Run()
   288  		t.Cleanup(bc.Close)
   289  	}
   290  	return bc, neotest.NewMultiSigner(multiValidatorAcc...), neotest.NewMultiSigner(multiCommitteeAcc...), err
   291  }