github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/internal/basicchain/basic.go (about) 1 package basicchain 2 3 import ( 4 "encoding/base64" 5 "encoding/hex" 6 "math/big" 7 "path" 8 "path/filepath" 9 "testing" 10 11 "github.com/nspcc-dev/neo-go/pkg/core/native" 12 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 13 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 14 "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" 15 "github.com/nspcc-dev/neo-go/pkg/io" 16 "github.com/nspcc-dev/neo-go/pkg/neotest" 17 "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" 18 "github.com/nspcc-dev/neo-go/pkg/util" 19 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 20 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 21 "github.com/nspcc-dev/neo-go/pkg/wallet" 22 "github.com/stretchr/testify/require" 23 ) 24 25 const neoAmount = 99999000 26 27 // Various contract IDs that were deployed to basic chain. 28 const ( 29 RublesContractID = int32(1) 30 VerifyContractID = int32(2) 31 VerifyWithArgsContractID = int32(3) 32 NNSContractID = int32(4) 33 NFSOContractID = int32(5) 34 StorageContractID = int32(6) 35 ) 36 37 const ( 38 // RublesOldTestvalue is a value initially stored by `testkey` key inside Rubles contract. 39 RublesOldTestvalue = "testvalue" 40 // RublesNewTestvalue is an updated value of Rubles' storage item with `testkey` key. 41 RublesNewTestvalue = "newtestvalue" 42 ) 43 44 // InitSimple initializes chain with simple contracts from 'examples' folder. 45 // It's not as complicated as chain got after Init and may be used for tests where 46 // chain with a small amount of data is needed and for historical functionality testing. 47 // Needs a path to the root directory. 48 func InitSimple(t *testing.T, rootpath string, e *neotest.Executor) { 49 // examplesPrefix is a prefix of the example smart-contracts. 50 var examplesPrefix = filepath.Join(rootpath, "examples") 51 52 deployExample := func(t *testing.T, name string) util.Uint160 { 53 _, h := newDeployTx(t, e, e.Validator, 54 filepath.Join(examplesPrefix, name, name+".go"), 55 filepath.Join(examplesPrefix, name, name+".yml"), 56 true) 57 return h 58 } 59 60 // Block #1: deploy storage contract (examples/storage/storage.go). 61 storageHash := deployExample(t, "storage") 62 storageValidatorInvoker := e.ValidatorInvoker(storageHash) 63 64 // Block #2: put (1, 1) kv pair. 65 storageValidatorInvoker.Invoke(t, 1, "put", 1, 1) 66 67 // Block #3: put (2, 2) kv pair. 68 storageValidatorInvoker.Invoke(t, 2, "put", 2, 2) 69 70 // Block #4: update (1, 1) -> (1, 2). 71 storageValidatorInvoker.Invoke(t, 1, "put", 1, 2) 72 73 // Block #5: deploy runtime contract (examples/runtime/runtime.go). 74 _ = deployExample(t, "runtime") 75 } 76 77 // Init pushes some predefined set of transactions into the given chain, it needs a path to 78 // the root project directory. 79 func Init(t *testing.T, rootpath string, e *neotest.Executor) { 80 if !e.Chain.GetConfig().P2PSigExtensions { 81 t.Fatal("P2PSigExtensions should be enabled to init basic chain") 82 } 83 84 var ( 85 // examplesPrefix is a prefix of the example smart-contracts. 86 examplesPrefix = filepath.Join(rootpath, "examples") 87 // testDataPrefix is used to retrieve smart contracts that should be deployed to 88 // Basic chain. 89 testDataPrefix = filepath.Join(rootpath, "internal", "basicchain", "testdata") 90 notaryModulePath = filepath.Join(rootpath, "pkg", "services", "notary") 91 ) 92 93 gasHash := e.NativeHash(t, nativenames.Gas) 94 neoHash := e.NativeHash(t, nativenames.Neo) 95 policyHash := e.NativeHash(t, nativenames.Policy) 96 notaryHash := e.NativeHash(t, nativenames.Notary) 97 designationHash := e.NativeHash(t, nativenames.Designation) 98 t.Logf("native GAS hash: %v", gasHash) 99 t.Logf("native NEO hash: %v", neoHash) 100 t.Logf("native Policy hash: %v", policyHash) 101 t.Logf("native Notary hash: %v", notaryHash) 102 t.Logf("Block0 hash: %s", e.Chain.GetHeaderHash(0).StringLE()) 103 104 acc0 := e.Validator.(neotest.MultiSigner).Single(2) // priv0 index->order and order->index conversion 105 priv0ScriptHash := acc0.ScriptHash() 106 acc1 := e.Validator.(neotest.MultiSigner).Single(0) // priv1 index->order and order->index conversion 107 priv1ScriptHash := acc1.ScriptHash() 108 neoValidatorInvoker := e.ValidatorInvoker(neoHash) 109 gasValidatorInvoker := e.ValidatorInvoker(gasHash) 110 neoPriv0Invoker := e.NewInvoker(neoHash, acc0) 111 gasPriv0Invoker := e.NewInvoker(gasHash, acc0) 112 designateSuperInvoker := e.NewInvoker(designationHash, e.Validator, e.Committee) 113 114 deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) { 115 txDeployHash, cH := newDeployTx(t, e, acc0, path, configPath, true) 116 b := e.TopBlock(t) 117 return b.Hash(), txDeployHash, cH 118 } 119 120 e.CheckGASBalance(t, priv0ScriptHash, big.NewInt(5000_0000)) // gas bounty 121 122 // Block #1: move 1000 GAS and neoAmount NEO to priv0. 123 txMoveNeo := neoValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, neoAmount, nil) 124 txMoveGas := gasValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) 125 b := e.AddNewBlock(t, txMoveNeo, txMoveGas) 126 e.CheckHalt(t, txMoveNeo.Hash(), stackitem.Make(true)) 127 e.CheckHalt(t, txMoveGas.Hash(), stackitem.Make(true)) 128 t.Logf("Block1 hash: %s", b.Hash().StringLE()) 129 bw := io.NewBufBinWriter() 130 b.EncodeBinary(bw.BinWriter) 131 require.NoError(t, bw.Err) 132 jsonB, err := b.MarshalJSON() 133 require.NoError(t, err) 134 t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) 135 t.Logf("Block1 JSON: %s", string(jsonB)) 136 bw.Reset() 137 b.Header.EncodeBinary(bw.BinWriter) 138 require.NoError(t, bw.Err) 139 jsonH, err := b.Header.MarshalJSON() 140 require.NoError(t, err) 141 t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) 142 t.Logf("Header1 JSON: %s", string(jsonH)) 143 jsonTxMoveNeo, err := txMoveNeo.MarshalJSON() 144 require.NoError(t, err) 145 t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE()) 146 t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo)) 147 t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes())) 148 t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE()) 149 150 e.EnsureGASBalance(t, priv0ScriptHash, func(balance *big.Int) bool { return balance.Cmp(big.NewInt(1000*native.GASFactor)) >= 0 }) 151 // info for getblockheader rpc tests 152 t.Logf("header hash: %s", b.Hash().StringLE()) 153 buf := io.NewBufBinWriter() 154 b.Header.EncodeBinary(buf.BinWriter) 155 t.Logf("header: %s", hex.EncodeToString(buf.Bytes())) 156 157 // Block #2: deploy test_contract (Rubles contract). 158 cfgPath := filepath.Join(testDataPrefix, "test_contract.yml") 159 block2H, txDeployH, cHash := deployContractFromPriv0(t, filepath.Join(testDataPrefix, "test_contract.go"), "Rubl", cfgPath, RublesContractID) 160 t.Logf("txDeploy: %s", txDeployH.StringLE()) 161 t.Logf("Block2 hash: %s", block2H.StringLE()) 162 163 // Block #3: invoke `putValue` method on the test_contract. 164 rublPriv0Invoker := e.NewInvoker(cHash, acc0) 165 txInvH := rublPriv0Invoker.Invoke(t, true, "putValue", "testkey", RublesOldTestvalue) 166 t.Logf("txInv: %s", txInvH.StringLE()) 167 168 // Block #4: transfer 1000 NEO from priv0 to priv1. 169 neoPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 1000, nil) 170 171 // Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0. 172 initTx := rublPriv0Invoker.PrepareInvoke(t, "init") 173 transferTx := e.NewUnsignedTx(t, rublPriv0Invoker.Hash, "transfer", cHash, priv0ScriptHash, 1000, nil) 174 e.SignTx(t, transferTx, 1500_0000, acc0) // Set system fee manually to avoid verification failure. 175 e.AddNewBlock(t, initTx, transferTx) 176 e.CheckHalt(t, initTx.Hash(), stackitem.NewBool(true)) 177 e.CheckHalt(t, transferTx.Hash(), stackitem.Make(true)) 178 t.Logf("receiveRublesTx: %v", transferTx.Hash().StringLE()) 179 180 // Block #6: transfer 123 rubles from priv0 to priv1 181 transferTxH := rublPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 123, nil) 182 t.Logf("sendRublesTx: %v", transferTxH.StringLE()) 183 184 // Block #7: push verification contract into the chain. 185 verifyPath := filepath.Join(testDataPrefix, "verify", "verification_contract.go") 186 verifyCfg := filepath.Join(testDataPrefix, "verify", "verification_contract.yml") 187 _, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", verifyCfg, VerifyContractID) 188 189 // Block #8: deposit some GAS to notary contract for priv0. 190 transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []any{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)}) 191 t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE()) 192 193 // Block #9: designate new Notary node. 194 ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json")) 195 require.NoError(t, err) 196 require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt)) 197 designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", 198 int64(noderoles.P2PNotary), []any{ntr.Accounts[0].PublicKey().Bytes()}) 199 t.Logf("Designated Notary node: %s", ntr.Accounts[0].PublicKey().StringCompressed()) 200 201 // Block #10: push verification contract with arguments into the chain. 202 verifyPath = filepath.Join(testDataPrefix, "verify_args", "verification_with_args_contract.go") 203 verifyCfg = filepath.Join(testDataPrefix, "verify_args", "verification_with_args_contract.yml") 204 _, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", verifyCfg, VerifyWithArgsContractID) // block #10 205 206 // Block #11: push NameService contract into the chain. 207 nsPath := filepath.Join(examplesPrefix, "nft-nd-nns") 208 nsConfigPath := filepath.Join(nsPath, "nns.yml") 209 _, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, NNSContractID) // block #11 210 nsCommitteeInvoker := e.CommitteeInvoker(nsHash) 211 nsPriv0Invoker := e.NewInvoker(nsHash, acc0) 212 213 // Block #12: transfer funds to committee for further NS record registration. 214 gasValidatorInvoker.Invoke(t, true, "transfer", 215 e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12 216 217 // Block #13: add `.com` root to NNS. 218 nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13 219 220 // Block #14: register `neo.com` via NNS. 221 registerTxH := nsPriv0Invoker.Invoke(t, true, "register", 222 "neo.com", priv0ScriptHash) // block #14 223 res := e.GetTxExecResult(t, registerTxH) 224 require.Equal(t, 1, len(res.Events)) // transfer 225 tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes() 226 require.NoError(t, err) 227 t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID)) 228 229 // Block #15: set A record type with priv0 owner via NNS. 230 nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15 231 232 // Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call 233 txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", RublesNewTestvalue) // tx1 234 // Invoke `test_contract.go`: put values to check `findstates` RPC call. 235 txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2 236 txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3 237 txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4 238 e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16 239 e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true)) 240 e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true)) 241 e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true)) 242 e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true)) 243 244 // Block #17: deploy NeoFS Object contract (NEP11-Divisible). 245 nfsPath := filepath.Join(examplesPrefix, "nft-d") 246 nfsConfigPath := filepath.Join(nfsPath, "nft.yml") 247 _, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, NFSOContractID) // block #17 248 nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0) 249 nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1) 250 251 // Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract. 252 containerID := util.Uint256{1, 2, 3} 253 objectID := util.Uint256{4, 5, 6} 254 txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer", 255 priv0ScriptHash, nfsHash, 10_0000_0000, []any{containerID.BytesBE(), objectID.BytesBE()}) // block #18 256 res = e.GetTxExecResult(t, txGas0toNFSH) 257 require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer 258 tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes() 259 require.NoError(t, err) 260 t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID)) 261 262 // Block #19: transfer 0.25 NFSO from priv0 to priv1. 263 nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19 264 265 // Block #20: transfer 1000 GAS to priv1. 266 gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), 267 priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20 268 269 // Block #21: transfer 0.05 NFSO from priv1 back to priv0. 270 nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21 271 272 // Block #22: deploy storage_contract (Storage contract for `traverseiterator` and `terminatesession` RPC calls test). 273 storagePath := filepath.Join(testDataPrefix, "storage", "storage_contract.go") 274 storageCfg := filepath.Join(testDataPrefix, "storage", "storage_contract.yml") 275 _, _, _ = deployContractFromPriv0(t, storagePath, "Storage", storageCfg, StorageContractID) 276 277 // Block #23: add FAULTed transaction to check WSClient waitloops. 278 faultedInvoker := e.NewInvoker(cHash, acc0) 279 faultedH := faultedInvoker.InvokeScriptCheckFAULT(t, []byte{byte(opcode.ABORT)}, []neotest.Signer{acc0}, "at instruction 0 (ABORT): ABORT") 280 t.Logf("FAULTed transaction:\n\thash LE: %s\n\tblock index: %d", faultedH.StringLE(), e.Chain.BlockHeight()) 281 282 // Compile contract to test `invokescript` RPC call 283 invokePath := filepath.Join(testDataPrefix, "invoke", "invokescript_contract.go") 284 invokeCfg := filepath.Join(testDataPrefix, "invoke", "invoke.yml") 285 _, _ = newDeployTx(t, e, acc0, invokePath, invokeCfg, false) 286 287 // Prepare some transaction for future submission. 288 txSendRaw := neoPriv0Invoker.PrepareInvoke(t, "transfer", priv0ScriptHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) 289 bw.Reset() 290 txSendRaw.EncodeBinary(bw.BinWriter) 291 t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE()) 292 293 sr20, err := e.Chain.GetStateModule().GetStateRoot(20) 294 require.NoError(t, err) 295 t.Logf("Block #20 stateroot LE: %s", sr20.Root.StringLE()) 296 } 297 298 func newDeployTx(t *testing.T, e *neotest.Executor, sender neotest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) { 299 c := neotest.CompileFile(t, sender.ScriptHash(), sourcePath, configPath) 300 t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", sourcePath, c.Hash.StringLE(), base64.StdEncoding.EncodeToString(c.NEF.Script)) 301 if deploy { 302 return e.DeployContractBy(t, sender, c, nil), c.Hash 303 } 304 return util.Uint256{}, c.Hash 305 }