github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/transaction/transaction_test.go (about) 1 package transaction 2 3 import ( 4 "encoding/base64" 5 "encoding/hex" 6 "encoding/json" 7 "math" 8 "testing" 9 10 "github.com/nspcc-dev/neo-go/internal/random" 11 "github.com/nspcc-dev/neo-go/internal/testserdes" 12 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 13 "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" 14 "github.com/nspcc-dev/neo-go/pkg/util" 15 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestWitnessEncodeDecode(t *testing.T) { 21 verif, err := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae") 22 assert.Nil(t, err) 23 invoc, err := hex.DecodeString("404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f4038a338f879930c8adc168983f60aae6f8542365d844f004976346b70fb0dd31aa1dbd4abd81e4a4aeef9941ecd4e2dd2c1a5b05e1cc74454d0403edaee6d7a4d4099d33c0b889bf6f3e6d87ab1b11140282e9a3265b0b9b918d6020b2c62d5a040c7e0c2c7c1dae3af9b19b178c71552ebd0b596e401c175067c70ea75717c8c00404e0ebd369e81093866fe29406dbf6b402c003774541799d08bf9bb0fc6070ec0f6bad908ab95f05fa64e682b485800b3c12102a8596e6c715ec76f4564d5eff34070e0521979fcd2cbbfa1456d97cc18d9b4a6ad87a97a2a0bcdedbf71b6c9676c645886056821b6f3fec8694894c66f41b762bc4e29e46ad15aee47f05d27d822") 24 assert.Nil(t, err) 25 26 lenInvoc := len(invoc) 27 lenVerif := len(verif) 28 t.Log(lenInvoc) 29 t.Log(lenVerif) 30 31 wit := &Witness{ 32 InvocationScript: invoc, 33 VerificationScript: verif, 34 } 35 36 witDecode := &Witness{} 37 testserdes.EncodeDecodeBinary(t, wit, witDecode) 38 39 t.Log(len(witDecode.VerificationScript)) 40 t.Log(len(witDecode.InvocationScript)) 41 } 42 43 func TestDecodeEncodeInvocationTX(t *testing.T) { 44 tx := decodeTransaction(rawInvocationTX, t) 45 46 script := "CwMAQNndiE0KAAwUgM7HtvW1b1BXj3N/Fi06sU1GZQ0MFN7uecGJ8wCYsLpqLrkLOpJYpsf/FMAfDAh0cmFuc2ZlcgwUz3bii9AGLEpHjuNVYQETGfPPpNJBYn1bUjk=" 47 assert.Equal(t, script, base64.StdEncoding.EncodeToString(tx.Script)) 48 assert.Equal(t, uint32(431760600), tx.Nonce) 49 assert.Equal(t, int64(11000000), tx.SystemFee) 50 assert.Equal(t, int64(4500000), tx.NetworkFee) 51 assert.Equal(t, uint32(1000), tx.ValidUntilBlock) 52 assert.Equal(t, "25426643feed564cd3e57f346d6c68692f5622b3063da11c5572d99ee1a5b49a", tx.Hash().StringLE()) 53 54 assert.Equal(t, 1, len(tx.Signers)) 55 assert.Equal(t, CalledByEntry, tx.Signers[0].Scopes) 56 assert.Equal(t, "ffc7a658923a0bb92e6abab09800f389c179eede", tx.Signers[0].Account.StringLE()) 57 58 assert.Equal(t, 0, len(tx.Attributes)) 59 invoc1 := "DEDWn0D7z2ELqpN8ghcM/PtfFwo56/BfEasfHuSKECJMYxvU47r2ZtSihg59lGxSZzHsvxTy6nsyvJ22ycNhINdJDECl61cg937N/HujKsLMu2wJMS7C54bzJ3q22Czqllvw3Yp809USgKDs+W+3QD7rI+SFs0OhIn0gooCUU6f/13WjDEDr9XdeT5CGTO8CL0JigzcTcucs0GBcqHs8fToO6zPuuCfS7Wh6dyxSCijT4A4S+7BUdW3dsO7828ke1fj8oNxm" 60 verif1 := "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl" 61 assert.Equal(t, 1, len(tx.Scripts)) 62 assert.Equal(t, invoc1, base64.StdEncoding.EncodeToString(tx.Scripts[0].InvocationScript)) 63 assert.Equal(t, verif1, base64.StdEncoding.EncodeToString(tx.Scripts[0].VerificationScript)) 64 65 data, err := testserdes.EncodeBinary(tx) 66 assert.NoError(t, err) 67 assert.Equal(t, rawInvocationTX, base64.StdEncoding.EncodeToString(data)) 68 } 69 70 func TestNew(t *testing.T) { 71 script := []byte{0x51} 72 tx := New(script, 1) 73 tx.Signers = []Signer{{Account: util.Uint160{1, 2, 3}}} 74 tx.Scripts = []Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}} 75 assert.Equal(t, int64(1), tx.SystemFee) 76 assert.Equal(t, script, tx.Script) 77 // Update hash fields to match tx2 that is gonna autoupdate them on decode. 78 _ = tx.Hash() 79 _ = tx.Size() 80 testserdes.EncodeDecodeBinary(t, tx, &Transaction{}) 81 } 82 83 func TestNewTransactionFromBytes(t *testing.T) { 84 script := []byte{0x51} 85 tx := New(script, 1) 86 tx.NetworkFee = 123 87 tx.Signers = []Signer{{Account: util.Uint160{1, 2, 3}}} 88 tx.Scripts = []Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}} 89 data, err := testserdes.EncodeBinary(tx) 90 require.NoError(t, err) 91 92 // set cached fields 93 tx.Hash() 94 tx.FeePerByte() 95 96 tx1, err := NewTransactionFromBytes(data) 97 require.NoError(t, err) 98 require.Equal(t, tx, tx1) 99 100 tx2 := new(Transaction) 101 err = testserdes.DecodeBinary(data, tx2) 102 require.NoError(t, err) 103 require.Equal(t, tx1, tx2) 104 105 data = append(data, 42) 106 _, err = NewTransactionFromBytes(data) 107 require.Error(t, err) 108 } 109 110 func TestEncodingTXWithNoScript(t *testing.T) { 111 _, err := testserdes.EncodeBinary(new(Transaction)) 112 require.NoError(t, err) // Garbage in -> garbage out. 113 } 114 115 func TestDecodingTXWithNoScript(t *testing.T) { 116 txBin, err := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 117 require.NoError(t, err) 118 err = testserdes.DecodeBinary(txBin, new(Transaction)) 119 require.Error(t, err) 120 } 121 122 func TestDecodingTxWithInvalidWitnessesNumber(t *testing.T) { 123 tx := New([]byte{byte(opcode.RET)}, 1) 124 tx.Signers = []Signer{{Account: util.Uint160{1, 2, 3}}} 125 tx.Scripts = []Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}, {InvocationScript: []byte{}, VerificationScript: []byte{}}} 126 data, err := testserdes.EncodeBinary(tx) 127 require.NoError(t, err) 128 require.ErrorIs(t, testserdes.DecodeBinary(data, new(Transaction)), ErrInvalidWitnessNum) 129 } 130 131 func TestUnmarshalNeoFSTX(t *testing.T) { 132 txjson := []byte(` 133 { 134 "hash": "0xc90c77d6c17fbb959b16df820370204a534ca13826cbd9fc027beb1b55d31e5a", 135 "size": 232, 136 "version": 0, 137 "nonce": 737880259, 138 "sender": "NiRqSd5MtRZT5yUhgWd7oG11brkDG76Jim", 139 "sysfee": "223719420", 140 "netfee": "1215550", 141 "validuntilblock": 1931, 142 "attributes": [], 143 "signers": [ 144 { 145 "account": "0x8f0ecd714c31c5624b6647e5fd661e5031c8f8f6", 146 "scopes": "Global" 147 } 148 ], 149 "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcIRwBESwAwEdm90ZQwUo4Hyc4fSGC6JbZtdrGb9LBbtWJtBYn1bUg==", 150 "witnesses": [ 151 { 152 "invocation": "DEDr2gA/8T/wxQvgOZVfCdkbj6uGrprkDgJvpOJCcbl+tvlKZkZytCZEWm6NoZhJyIlEI3VQSLtU3AHuJfShAT5L", 153 "verification": "DCEDAS1H52IQrsc745qz0YbgpA/o2Gv6PU+r/aV7oTuI+WoLQZVEDXg=" 154 } 155 ] 156 }`) 157 tx := new(Transaction) 158 require.NoError(t, json.Unmarshal(txjson, tx)) 159 } 160 161 func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) { 162 tx := &Transaction{ 163 Version: 0, 164 Signers: []Signer{{Account: util.Uint160{1, 2, 3}}}, 165 Script: []byte{1, 2, 3, 4}, 166 Attributes: []Attribute{{Type: HighPriority}}, 167 Scripts: []Witness{}, 168 SystemFee: int64(fixedn.Fixed8FromFloat(123.45)), 169 NetworkFee: int64(fixedn.Fixed8FromFloat(0.123)), 170 Trimmed: false, 171 } 172 173 testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) 174 } 175 176 func TestTransaction_HasAttribute(t *testing.T) { 177 tx := New([]byte{1}, 0) 178 require.False(t, tx.HasAttribute(HighPriority)) 179 tx.Attributes = append(tx.Attributes, Attribute{Type: HighPriority}) 180 require.True(t, tx.HasAttribute(HighPriority)) 181 tx.Attributes = append(tx.Attributes, Attribute{Type: HighPriority}) 182 require.True(t, tx.HasAttribute(HighPriority)) 183 } 184 185 func TestTransaction_isValid(t *testing.T) { 186 newTx := func() *Transaction { 187 return &Transaction{ 188 Version: 0, 189 SystemFee: 100, 190 NetworkFee: 100, 191 Signers: []Signer{ 192 {Account: util.Uint160{1, 2, 3}}, 193 { 194 Account: util.Uint160{4, 5, 6}, 195 Scopes: Global, 196 }, 197 }, 198 Script: []byte{1, 2, 3, 4}, 199 Attributes: []Attribute{}, 200 Scripts: []Witness{}, 201 Trimmed: false, 202 } 203 } 204 205 t.Run("Valid", func(t *testing.T) { 206 t.Run("NoAttributes", func(t *testing.T) { 207 tx := newTx() 208 require.NoError(t, tx.isValid()) 209 }) 210 t.Run("HighPriority", func(t *testing.T) { 211 tx := newTx() 212 tx.Attributes = []Attribute{{Type: HighPriority}} 213 require.NoError(t, tx.isValid()) 214 }) 215 }) 216 t.Run("InvalidVersion", func(t *testing.T) { 217 tx := newTx() 218 tx.Version = 1 219 require.ErrorIs(t, tx.isValid(), ErrInvalidVersion) 220 }) 221 t.Run("NegativeSystemFee", func(t *testing.T) { 222 tx := newTx() 223 tx.SystemFee = -1 224 require.ErrorIs(t, tx.isValid(), ErrNegativeSystemFee) 225 }) 226 t.Run("NegativeNetworkFee", func(t *testing.T) { 227 tx := newTx() 228 tx.NetworkFee = -1 229 require.ErrorIs(t, tx.isValid(), ErrNegativeNetworkFee) 230 }) 231 t.Run("TooBigFees", func(t *testing.T) { 232 tx := newTx() 233 tx.SystemFee = math.MaxInt64 - tx.NetworkFee + 1 234 require.ErrorIs(t, tx.isValid(), ErrTooBigFees) 235 }) 236 t.Run("EmptySigners", func(t *testing.T) { 237 tx := newTx() 238 tx.Signers = tx.Signers[:0] 239 require.ErrorIs(t, tx.isValid(), ErrEmptySigners) 240 }) 241 t.Run("NonUniqueSigners", func(t *testing.T) { 242 tx := newTx() 243 tx.Signers[1].Account = tx.Signers[0].Account 244 require.ErrorIs(t, tx.isValid(), ErrNonUniqueSigners) 245 }) 246 t.Run("MultipleHighPriority", func(t *testing.T) { 247 tx := newTx() 248 tx.Attributes = []Attribute{ 249 {Type: HighPriority}, 250 {Type: HighPriority}, 251 } 252 require.ErrorIs(t, tx.isValid(), ErrInvalidAttribute) 253 }) 254 t.Run("MultipleOracle", func(t *testing.T) { 255 tx := newTx() 256 tx.Attributes = []Attribute{ 257 {Type: OracleResponseT}, 258 {Type: OracleResponseT}, 259 } 260 require.ErrorIs(t, tx.isValid(), ErrInvalidAttribute) 261 }) 262 t.Run("NoScript", func(t *testing.T) { 263 tx := newTx() 264 tx.Script = []byte{} 265 require.ErrorIs(t, tx.isValid(), ErrEmptyScript) 266 }) 267 } 268 269 func TestTransaction_GetAttributes(t *testing.T) { 270 attributesTypes := []AttrType{ 271 HighPriority, 272 OracleResponseT, 273 NotValidBeforeT, 274 } 275 t.Run("no attributes", func(t *testing.T) { 276 tx := new(Transaction) 277 for _, typ := range attributesTypes { 278 require.Nil(t, tx.GetAttributes(typ)) 279 } 280 }) 281 t.Run("single attributes", func(t *testing.T) { 282 attrs := make([]Attribute, len(attributesTypes)) 283 for i, typ := range attributesTypes { 284 attrs[i] = Attribute{Type: typ} 285 } 286 tx := &Transaction{Attributes: attrs} 287 for _, typ := range attributesTypes { 288 require.Equal(t, []Attribute{{Type: typ}}, tx.GetAttributes(typ)) 289 } 290 }) 291 t.Run("multiple attributes", func(t *testing.T) { 292 typ := AttrType(ReservedLowerBound + 1) 293 conflictsAttrs := []Attribute{{Type: typ}, {Type: typ}} 294 tx := Transaction{ 295 Attributes: append([]Attribute{{Type: HighPriority}}, conflictsAttrs...), 296 } 297 require.Equal(t, conflictsAttrs, tx.GetAttributes(typ)) 298 }) 299 } 300 301 func TestTransaction_HasSigner(t *testing.T) { 302 u1, u2 := random.Uint160(), random.Uint160() 303 tx := Transaction{ 304 Signers: []Signer{ 305 {Account: u1}, {Account: u2}, 306 }, 307 } 308 require.True(t, tx.HasSigner(u1)) 309 require.False(t, tx.HasSigner(util.Uint160{})) 310 } 311 312 func BenchmarkTxHash(b *testing.B) { 313 script := []byte{0x51} 314 tx := New(script, 1) 315 tx.NetworkFee = 123 316 tx.Signers = []Signer{{Account: util.Uint160{1, 2, 3}}} 317 tx.Scripts = []Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}} 318 319 // Prime cache. 320 tx.Hash() 321 for i := 0; i < b.N; i++ { 322 _ = tx.Hash() 323 } 324 } 325 326 func TestTransaction_DeepCopy(t *testing.T) { 327 origTx := New([]byte{0x01, 0x02, 0x03}, 1000) 328 origTx.NetworkFee = 2000 329 origTx.SystemFee = 500 330 origTx.Nonce = 12345678 331 origTx.ValidUntilBlock = 100 332 origTx.Version = 1 333 require.Nil(t, (*Transaction)(nil).Copy()) 334 335 priv, err := keys.NewPrivateKey() 336 require.NoError(t, err) 337 origTx.Signers = []Signer{ 338 {Account: random.Uint160(), Scopes: Global, AllowedContracts: []util.Uint160{random.Uint160()}, AllowedGroups: keys.PublicKeys{priv.PublicKey()}, Rules: []WitnessRule{{Action: 0x01, Condition: ConditionCalledByEntry{}}}}, 339 {Account: random.Uint160(), Scopes: CalledByEntry}, 340 } 341 origTx.Attributes = []Attribute{ 342 {Type: HighPriority, Value: &OracleResponse{ 343 ID: 0, 344 Code: Success, 345 Result: []byte{4, 8, 15, 16, 23, 42}, 346 }}, 347 } 348 origTx.Scripts = []Witness{ 349 { 350 InvocationScript: []byte{0x04, 0x05}, 351 VerificationScript: []byte{0x06, 0x07}, 352 }, 353 } 354 origTxHash := origTx.Hash() 355 356 copyTx := origTx.Copy() 357 358 require.Equal(t, origTx.Hash(), copyTx.Hash()) 359 require.Equal(t, origTx, copyTx) 360 require.Equal(t, origTx.Size(), copyTx.Size()) 361 362 copyTx.NetworkFee = 3000 363 copyTx.Signers[0].Scopes = None 364 copyTx.Attributes[0].Type = NotaryAssistedT 365 copyTx.Scripts[0].InvocationScript[0] = 0x08 366 copyTx.hashed = false 367 modifiedCopyTxHash := copyTx.Hash() 368 369 require.NotEqual(t, origTx.NetworkFee, copyTx.NetworkFee) 370 require.NotEqual(t, origTx.Signers[0].Scopes, copyTx.Signers[0].Scopes) 371 require.NotEqual(t, origTx.Attributes, copyTx.Attributes) 372 require.NotEqual(t, origTxHash, modifiedCopyTxHash) 373 374 require.NotEqual(t, &origTx.Scripts[0].InvocationScript[0], ©Tx.Scripts[0].InvocationScript[0]) 375 require.NotEqual(t, &origTx.Scripts, ©Tx.Scripts) 376 require.Equal(t, origTx.Scripts[0].VerificationScript, copyTx.Scripts[0].VerificationScript) 377 require.Equal(t, origTx.Signers[0].AllowedContracts, copyTx.Signers[0].AllowedContracts) 378 require.Equal(t, origTx.Signers[0].AllowedGroups, copyTx.Signers[0].AllowedGroups) 379 origGroup := origTx.Signers[0].AllowedGroups[0] 380 copyGroup := copyTx.Signers[0].AllowedGroups[0] 381 require.True(t, origGroup.Equal(copyGroup)) 382 383 copyTx.Signers[0].AllowedGroups[0] = nil 384 require.NotEqual(t, origTx.Signers[0].AllowedGroups[0], copyTx.Signers[0].AllowedGroups[0]) 385 386 require.Equal(t, origTx.Signers[0].Rules[0], copyTx.Signers[0].Rules[0]) 387 copyTx.Signers[0].Rules[0].Action = 0x02 388 require.NotEqual(t, origTx.Signers[0].Rules[0].Action, copyTx.Signers[0].Rules[0].Action) 389 }