github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/native_test/ledger_test.go (about)

     1  package native_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/compiler"
    10  	"github.com/nspcc-dev/neo-go/pkg/config"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    13  	"github.com/nspcc-dev/neo-go/pkg/neotest"
    14  	"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
    15  	"github.com/nspcc-dev/neo-go/pkg/util"
    16  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    17  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    18  	"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  func newLedgerClient(t *testing.T) *neotest.ContractInvoker {
    23  	bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.Blockchain) {
    24  		cfg.MaxTraceableBlocks = 10 // reduce number of traceable blocks for Ledger tests
    25  	})
    26  	e := neotest.NewExecutor(t, bc, acc, acc)
    27  
    28  	return e.CommitteeInvoker(e.NativeHash(t, nativenames.Ledger))
    29  }
    30  
    31  func TestLedger_GetTransactionHeight(t *testing.T) {
    32  	c := newLedgerClient(t)
    33  	e := c.Executor
    34  	ledgerInvoker := c.WithSigners(c.Committee)
    35  
    36  	height := 13
    37  	e.GenerateNewBlocks(t, height-1)
    38  	hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
    39  
    40  	t.Run("good", func(t *testing.T) {
    41  		ledgerInvoker.Invoke(t, height, "getTransactionHeight", hash)
    42  	})
    43  	t.Run("unknown transaction", func(t *testing.T) {
    44  		ledgerInvoker.Invoke(t, -1, "getTransactionHeight", util.Uint256{1, 2, 3})
    45  	})
    46  	t.Run("not a hash", func(t *testing.T) {
    47  		ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionHeight", []byte{1, 2, 3})
    48  	})
    49  }
    50  
    51  func TestLedger_GetTransactionState(t *testing.T) {
    52  	c := newLedgerClient(t)
    53  	e := c.Executor
    54  	ledgerInvoker := c.WithSigners(c.Committee)
    55  
    56  	hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
    57  
    58  	t.Run("unknown transaction", func(t *testing.T) {
    59  		ledgerInvoker.Invoke(t, vmstate.None, "getTransactionVMState", util.Uint256{1, 2, 3})
    60  	})
    61  	t.Run("not a hash", func(t *testing.T) {
    62  		ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionVMState", []byte{1, 2, 3})
    63  	})
    64  	t.Run("good: HALT", func(t *testing.T) {
    65  		ledgerInvoker.Invoke(t, vmstate.Halt, "getTransactionVMState", hash)
    66  	})
    67  	t.Run("isn't traceable", func(t *testing.T) {
    68  		// Add more blocks so that tx becomes untraceable.
    69  		e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
    70  		ledgerInvoker.Invoke(t, vmstate.None, "getTransactionVMState", hash)
    71  	})
    72  	t.Run("good: FAULT", func(t *testing.T) {
    73  		faultedH := e.InvokeScript(t, []byte{byte(opcode.ABORT)}, []neotest.Signer{c.Committee})
    74  		ledgerInvoker.Invoke(t, vmstate.Fault, "getTransactionVMState", faultedH)
    75  	})
    76  }
    77  
    78  func TestLedger_GetTransaction(t *testing.T) {
    79  	c := newLedgerClient(t)
    80  	e := c.Executor
    81  	ledgerInvoker := c.WithSigners(c.Committee)
    82  
    83  	hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
    84  	tx, _ := e.GetTransaction(t, hash)
    85  
    86  	t.Run("success", func(t *testing.T) {
    87  		ledgerInvoker.Invoke(t, []stackitem.Item{
    88  			stackitem.NewByteArray(tx.Hash().BytesBE()),
    89  			stackitem.NewBigInteger(big.NewInt(int64(tx.Version))),
    90  			stackitem.NewBigInteger(big.NewInt(int64(tx.Nonce))),
    91  			stackitem.NewByteArray(tx.Sender().BytesBE()),
    92  			stackitem.NewBigInteger(big.NewInt(tx.SystemFee)),
    93  			stackitem.NewBigInteger(big.NewInt(tx.NetworkFee)),
    94  			stackitem.NewBigInteger(big.NewInt(int64(tx.ValidUntilBlock))),
    95  			stackitem.NewByteArray(tx.Script),
    96  		}, "getTransaction", tx.Hash())
    97  	})
    98  	t.Run("isn't traceable", func(t *testing.T) {
    99  		// Add more blocks so that tx becomes untraceable.
   100  		e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
   101  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", tx.Hash())
   102  	})
   103  	t.Run("bad hash", func(t *testing.T) {
   104  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", util.Uint256{})
   105  	})
   106  }
   107  
   108  func TestLedger_GetTransactionFromBlock(t *testing.T) {
   109  	c := newLedgerClient(t)
   110  	e := c.Executor
   111  	ledgerInvoker := c.WithSigners(c.Committee)
   112  
   113  	ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block.
   114  	b := e.GetBlockByIndex(t, e.Chain.BlockHeight())
   115  
   116  	check := func(t testing.TB, stack []stackitem.Item) {
   117  		require.Equal(t, 1, len(stack))
   118  		actual, ok := stack[0].Value().([]stackitem.Item)
   119  		require.True(t, ok)
   120  		require.Equal(t, b.Transactions[0].Hash().BytesBE(), actual[0].Value().([]byte))
   121  	}
   122  	t.Run("good, by hash", func(t *testing.T) {
   123  		ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", b.Hash(), int64(0))
   124  	})
   125  	t.Run("good, by index", func(t *testing.T) {
   126  		ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", int64(b.Index), int64(0))
   127  	})
   128  	t.Run("bad transaction index", func(t *testing.T) {
   129  		ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash(), int64(1))
   130  	})
   131  	t.Run("bad block hash (>int64)", func(t *testing.T) {
   132  		ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:10], int64(0))
   133  	})
   134  	t.Run("invalid block hash (int64)", func(t *testing.T) {
   135  		ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:6], int64(0))
   136  	})
   137  	t.Run("unknown block hash", func(t *testing.T) {
   138  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash().BytesLE(), int64(0))
   139  	})
   140  	t.Run("isn't traceable", func(t *testing.T) {
   141  		e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
   142  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash(), int64(0))
   143  	})
   144  }
   145  
   146  func TestLedger_GetBlock(t *testing.T) {
   147  	c := newLedgerClient(t)
   148  	e := c.Executor
   149  	ledgerInvoker := c.WithSigners(c.Committee)
   150  
   151  	ledgerInvoker.Invoke(t, e.Chain.GetHeaderHash(e.Chain.BlockHeight()).BytesBE(), "currentHash") // Adds a block.
   152  	b := e.GetBlockByIndex(t, e.Chain.BlockHeight())
   153  
   154  	expected := []stackitem.Item{
   155  		stackitem.NewByteArray(b.Hash().BytesBE()),
   156  		stackitem.NewBigInteger(big.NewInt(int64(b.Version))),
   157  		stackitem.NewByteArray(b.PrevHash.BytesBE()),
   158  		stackitem.NewByteArray(b.MerkleRoot.BytesBE()),
   159  		stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))),
   160  		stackitem.NewBigInteger(big.NewInt(int64(b.Nonce))),
   161  		stackitem.NewBigInteger(big.NewInt(int64(b.Index))),
   162  		stackitem.NewByteArray(b.NextConsensus.BytesBE()),
   163  		stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))),
   164  	}
   165  	t.Run("good, by hash", func(t *testing.T) {
   166  		ledgerInvoker.Invoke(t, expected, "getBlock", b.Hash())
   167  	})
   168  	t.Run("good, by index", func(t *testing.T) {
   169  		ledgerInvoker.Invoke(t, expected, "getBlock", int64(b.Index))
   170  	})
   171  	t.Run("bad hash", func(t *testing.T) {
   172  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash().BytesLE())
   173  	})
   174  	t.Run("isn't traceable", func(t *testing.T) {
   175  		e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
   176  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash())
   177  	})
   178  }
   179  
   180  func TestLedger_GetTransactionSigners(t *testing.T) {
   181  	c := newLedgerClient(t)
   182  	e := c.Executor
   183  	ledgerInvoker := c.WithSigners(c.Committee)
   184  
   185  	txHash := ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex")
   186  
   187  	t.Run("good", func(t *testing.T) {
   188  		s := &transaction.Signer{
   189  			Account: c.CommitteeHash,
   190  			Scopes:  transaction.Global,
   191  		}
   192  		expected := stackitem.NewArray([]stackitem.Item{
   193  			stackitem.NewArray([]stackitem.Item{
   194  				stackitem.NewByteArray(s.Account.BytesBE()),
   195  				stackitem.NewBigInteger(big.NewInt(int64(s.Scopes))),
   196  				stackitem.NewArray([]stackitem.Item{}),
   197  				stackitem.NewArray([]stackitem.Item{}),
   198  				stackitem.NewArray([]stackitem.Item{}),
   199  			}),
   200  		})
   201  		ledgerInvoker.Invoke(t, expected, "getTransactionSigners", txHash)
   202  	})
   203  	t.Run("unknown transaction", func(t *testing.T) {
   204  		ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionSigners", util.Uint256{1, 2, 3})
   205  	})
   206  	t.Run("not a hash", func(t *testing.T) {
   207  		ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionSigners", []byte{1, 2, 3})
   208  	})
   209  }
   210  
   211  func TestLedger_GetTransactionSignersInteropAPI(t *testing.T) {
   212  	c := newLedgerClient(t)
   213  	e := c.Executor
   214  	ledgerInvoker := c.WithSigners(c.Committee)
   215  
   216  	// Firstly, add transaction with CalledByEntry rule-based signer scope to the chain.
   217  	tx := e.NewUnsignedTx(t, ledgerInvoker.Hash, "currentIndex")
   218  	tx.Signers = []transaction.Signer{{
   219  		Account: c.Committee.ScriptHash(),
   220  		Scopes:  transaction.Rules,
   221  		Rules: []transaction.WitnessRule{
   222  			{
   223  				Action:    transaction.WitnessAllow,
   224  				Condition: transaction.ConditionCalledByEntry{},
   225  			},
   226  		},
   227  	}}
   228  	neotest.AddNetworkFee(t, e.Chain, tx, c.Committee)
   229  	neotest.AddSystemFee(e.Chain, tx, -1)
   230  	require.NoError(t, c.Committee.SignTx(e.Chain.GetConfig().Magic, tx))
   231  	c.AddNewBlock(t, tx)
   232  	c.CheckHalt(t, tx.Hash(), stackitem.Make(e.Chain.BlockHeight()-1))
   233  
   234  	var (
   235  		hashStr string
   236  		accStr  string
   237  		txHash  = tx.Hash().BytesBE()
   238  		acc     = c.Committee.ScriptHash().BytesBE()
   239  	)
   240  	for i := 0; i < util.Uint256Size; i++ {
   241  		hashStr += fmt.Sprintf("%#x", txHash[i])
   242  		if i != util.Uint256Size-1 {
   243  			hashStr += ", "
   244  		}
   245  	}
   246  	for i := 0; i < util.Uint160Size; i++ {
   247  		accStr += fmt.Sprintf("%#x", acc[i])
   248  		if i != util.Uint160Size-1 {
   249  			accStr += ", "
   250  		}
   251  	}
   252  
   253  	// After that ensure interop API allows to retrieve signer with CalledByEntry rule-based scope.
   254  	src := `package callledger
   255  		import (
   256  			"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
   257  			"github.com/nspcc-dev/neo-go/pkg/interop"
   258  			"github.com/nspcc-dev/neo-go/pkg/interop/util"
   259  		)
   260  		func CallLedger(accessValue bool) int {
   261  			signers := ledger.GetTransactionSigners(interop.Hash256{` + hashStr + `})
   262  			if len(signers) != 1 {
   263  				panic("bad length")
   264  			}
   265  			s0 := signers[0]
   266  			expectedAcc := interop.Hash160{` + accStr + `}
   267  			if !util.Equals(string(s0.Account), string(expectedAcc)) {
   268  				panic("bad account")
   269  			}
   270  			if s0.Scopes != ledger.Rules {
   271  				panic("bad signer scope")
   272  			}
   273  			if len(s0.Rules) != 1 {
   274  				panic("bad rules length")
   275  			}
   276  			r0 := s0.Rules[0]
   277  			if r0.Action != ledger.WitnessAllow {
   278  				panic("bad action")
   279  			}
   280  			c0 := r0.Condition
   281  			if c0.Type != ledger.WitnessCalledByEntry {
   282  				panic("bad condition type")
   283  			}
   284  			if accessValue {
   285  				// Panic should occur here, because there's only Type inside the CalledByEntry condition.
   286  				_ = c0.Value
   287  			}
   288  			return 1
   289  		}`
   290  	ctr := neotest.CompileSource(t, c.Committee.ScriptHash(), strings.NewReader(src), &compiler.Options{
   291  		Name: "calledger_contract",
   292  	})
   293  	e.DeployContract(t, ctr, nil)
   294  
   295  	ctrInvoker := e.NewInvoker(ctr.Hash, e.Committee)
   296  	ctrInvoker.Invoke(t, 1, "callLedger", false)                                                                    // Firstly, don't access CalledByEnrty Condition value => the call should be successful.
   297  	ctrInvoker.InvokeFail(t, `(PICKITEM): unhandled exception: "The value 1 is out of range."`, "callLedger", true) // Then, access the value to ensure it will panic.
   298  }