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 }