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 }