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  }