diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 830712741..93a3cc46f 100644 Binary files a/cli/testdata/chain50x2.acc and b/cli/testdata/chain50x2.acc differ diff --git a/docs/rpc.md b/docs/rpc.md index 4f0462271..68c68fdf9 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -84,6 +84,11 @@ node where it only works for addresses from opened wallet. It's possible to get non-native contract state by its ID, unlike with C# node where it only works for native contracts. +##### `getstorage` + +This method doesn't work for the Ledger contract, you can get data via regular +`getblock` and `getrawtransaction` calls. + ### Unsupported methods Methods listed down below are not going to be supported for various reasons diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index e91acd718..fe8588ddf 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -239,7 +239,7 @@ func scAndVMInteropTypeFromExpr(named *types.Named) (smartcontract.ParamType, st name := named.Obj().Name() pkg := named.Obj().Pkg().Name() switch pkg { - case "blockchain", "contract": + case "runtime", "contract": return smartcontract.ArrayType, stackitem.ArrayT // Block, Transaction, Contract case "interop": if name != "Interface" { diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index ab2ba254a..04f545384 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -16,7 +16,7 @@ func TestCodeGen_DebugInfo(t *testing.T) { src := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop/storage" - import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" + import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" func Main(op string) bool { var s string _ = s @@ -47,7 +47,7 @@ func unexportedMethod() int { return 1 } func MethodParams(addr interop.Hash160, h interop.Hash256, sig interop.Signature, pub interop.PublicKey, inter interop.Interface, - ctx storage.Context, tx blockchain.Transaction) bool { + ctx storage.Context, tx runtime.Transaction) bool { return true } type MyStruct struct {} diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index ead24c899..a82002da0 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -14,13 +14,6 @@ var syscalls = map[string]map[string]string{ "Itoa": interopnames.SystemBinaryItoa, "Serialize": interopnames.SystemBinarySerialize, }, - "blockchain": { - "GetBlock": interopnames.SystemBlockchainGetBlock, - "GetHeight": interopnames.SystemBlockchainGetHeight, - "GetTransaction": interopnames.SystemBlockchainGetTransaction, - "GetTransactionFromBlock": interopnames.SystemBlockchainGetTransactionFromBlock, - "GetTransactionHeight": interopnames.SystemBlockchainGetTransactionHeight, - }, "contract": { "Call": interopnames.SystemContractCall, "CreateStandardAccount": interopnames.SystemContractCreateStandardAccount, diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 20481bd02..3173bb3ed 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1744,7 +1744,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, if bc.contracts.ByHash(hash) != nil { return ErrNativeContractWitness } - v.LoadScriptWithFlags(witness.VerificationScript, callflag.NoneFlag) + v.LoadScriptWithFlags(witness.VerificationScript, callflag.ReadStates) } else { cs, err := ic.GetContract(hash) if err != nil { diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 624eb5d05..7d72223c1 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -2,10 +2,8 @@ package core import ( "errors" - "fmt" "math/big" "math/rand" - "strings" "testing" "time" @@ -1165,12 +1163,16 @@ func TestIsTxStillRelevant(t *testing.T) { require.NoError(t, bc.AddBlock(bc.newBlock())) require.True(t, bc.IsTxStillRelevant(tx3, nil, false)) }) + /* // neo-project/neo#2289 t.Run("contract witness check fails", func(t *testing.T) { src := fmt.Sprintf(`package verify - import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" + import ( + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/util" + ) func Verify() bool { - currentHeight := blockchain.GetHeight() - return currentHeight < %d + currentHeight := contract.Call(util.FromAddress("NV5WuMGkwhQexQ4afTwuRojMeWwfWrEAdv"), "currentIndex", contract.ReadStates) + return currentHeight.(int) < %d }`, bc.BlockHeight()+2) // deploy + next block txDeploy, h, err := testchain.NewDeployTx(bc, "TestVerify", neoOwner, strings.NewReader(src)) require.NoError(t, err) @@ -1184,7 +1186,7 @@ func TestIsTxStillRelevant(t *testing.T) { Account: h, Scopes: transaction.None, }) - tx.NetworkFee += 1_000_000 + tx.NetworkFee += 10_000_000 require.NoError(t, testchain.SignTx(bc, tx)) tx.Scripts = append(tx.Scripts, transaction.Witness{}) @@ -1192,6 +1194,7 @@ func TestIsTxStillRelevant(t *testing.T) { require.NoError(t, bc.AddBlock(bc.newBlock())) require.False(t, bc.IsTxStillRelevant(tx, mp, false)) }) + */ } func TestMemPoolRemoval(t *testing.T) { diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 8a76df3dd..b4975e090 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -10,11 +10,6 @@ const ( SystemBinaryDeserialize = "System.Binary.Deserialize" SystemBinaryItoa = "System.Binary.Itoa" SystemBinarySerialize = "System.Binary.Serialize" - SystemBlockchainGetBlock = "System.Blockchain.GetBlock" - SystemBlockchainGetHeight = "System.Blockchain.GetHeight" - SystemBlockchainGetTransaction = "System.Blockchain.GetTransaction" - SystemBlockchainGetTransactionFromBlock = "System.Blockchain.GetTransactionFromBlock" - SystemBlockchainGetTransactionHeight = "System.Blockchain.GetTransactionHeight" SystemCallbackCreate = "System.Callback.Create" SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" SystemCallbackCreateFromSyscall = "System.Callback.CreateFromSyscall" @@ -69,11 +64,6 @@ var names = []string{ SystemBinaryDeserialize, SystemBinaryItoa, SystemBinarySerialize, - SystemBlockchainGetBlock, - SystemBlockchainGetHeight, - SystemBlockchainGetTransaction, - SystemBlockchainGetTransactionFromBlock, - SystemBlockchainGetTransactionHeight, SystemCallbackCreate, SystemCallbackCreateFromMethod, SystemCallbackCreateFromSyscall, diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 7fc48ea5f..d6ffc3836 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -4,13 +4,10 @@ import ( "crypto/elliptic" "errors" "fmt" - "math" - "math/big" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" - "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -45,144 +42,15 @@ const ( Constant StorageFlag = 0x01 ) -// getBlockHashFromElement converts given vm.Element to block hash using given -// Blockchainer if needed. Interop functions accept both block numbers and -// block hashes as parameters, thus this function is needed. -func getBlockHashFromElement(bc blockchainer.Blockchainer, element *vm.Element) (util.Uint256, error) { - var hash util.Uint256 - hashbytes := element.Bytes() - if len(hashbytes) <= 5 { - hashint := element.BigInt().Int64() - if hashint < 0 || hashint > math.MaxUint32 { - return hash, errors.New("bad block index") - } - hash = bc.GetHeaderHash(int(hashint)) - } else { - return util.Uint256DecodeBytesBE(hashbytes) - } - return hash, nil -} - -// blockToStackItem converts block.Block to stackitem.Item -func blockToStackItem(b *block.Block) stackitem.Item { - return stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(b.Hash().BytesBE()), - stackitem.NewBigInteger(big.NewInt(int64(b.Version))), - stackitem.NewByteArray(b.PrevHash.BytesBE()), - stackitem.NewByteArray(b.MerkleRoot.BytesBE()), - stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))), - stackitem.NewBigInteger(big.NewInt(int64(b.Index))), - stackitem.NewByteArray(b.NextConsensus.BytesBE()), - stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))), - }) -} - -// bcGetBlock returns current block. -func bcGetBlock(ic *interop.Context) error { - hash, err := getBlockHashFromElement(ic.Chain, ic.VM.Estack().Pop()) - if err != nil { - return err - } - block, err := ic.Chain.GetBlock(hash) - if err != nil || !isTraceableBlock(ic, block.Index) { - ic.VM.Estack().PushVal(stackitem.Null{}) - } else { - ic.VM.Estack().PushVal(blockToStackItem(block)) - } - return nil -} - -// bcGetHeight returns blockchain height. -func bcGetHeight(ic *interop.Context) error { - ic.VM.Estack().PushVal(ic.Chain.BlockHeight()) - return nil -} - -// getTransactionAndHeight gets parameter from the vm evaluation stack and -// returns transaction and its height if it's present in the blockchain. -func getTransactionAndHeight(cd *dao.Cached, v *vm.VM) (*transaction.Transaction, uint32, error) { - hashbytes := v.Estack().Pop().Bytes() - hash, err := util.Uint256DecodeBytesBE(hashbytes) - if err != nil { - return nil, 0, err - } - return cd.GetTransaction(hash) -} - -// isTraceableBlock defines whether we're able to give information about -// the block with index specified. -func isTraceableBlock(ic *interop.Context, index uint32) bool { - height := ic.Chain.BlockHeight() - MaxTraceableBlocks := ic.Chain.GetConfig().MaxTraceableBlocks - return index <= height && index+MaxTraceableBlocks > height -} - -// transactionToStackItem converts transaction.Transaction to stackitem.Item -func transactionToStackItem(t *transaction.Transaction) stackitem.Item { - return stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(t.Hash().BytesBE()), - stackitem.NewBigInteger(big.NewInt(int64(t.Version))), - stackitem.NewBigInteger(big.NewInt(int64(t.Nonce))), - stackitem.NewByteArray(t.Sender().BytesBE()), - stackitem.NewBigInteger(big.NewInt(int64(t.SystemFee))), - stackitem.NewBigInteger(big.NewInt(int64(t.NetworkFee))), - stackitem.NewBigInteger(big.NewInt(int64(t.ValidUntilBlock))), - stackitem.NewByteArray(t.Script), - }) -} - -// bcGetTransaction returns transaction. -func bcGetTransaction(ic *interop.Context) error { - tx, h, err := getTransactionAndHeight(ic.DAO, ic.VM) - if err != nil || !isTraceableBlock(ic, h) { - ic.VM.Estack().PushVal(stackitem.Null{}) - return nil - } - ic.VM.Estack().PushVal(transactionToStackItem(tx)) - return nil -} - -// bcGetTransactionFromBlock returns transaction with the given index from the -// block with height or hash specified. -func bcGetTransactionFromBlock(ic *interop.Context) error { - hash, err := getBlockHashFromElement(ic.Chain, ic.VM.Estack().Pop()) - if err != nil { - return err - } - index := ic.VM.Estack().Pop().BigInt().Int64() - block, err := ic.DAO.GetBlock(hash) - if err != nil || !isTraceableBlock(ic, block.Index) { - ic.VM.Estack().PushVal(stackitem.Null{}) - return nil - } - if index < 0 || index >= int64(len(block.Transactions)) { - return errors.New("wrong transaction index") - } - tx := block.Transactions[index] - ic.VM.Estack().PushVal(tx.Hash().BytesBE()) - return nil -} - -// bcGetTransactionHeight returns transaction height. -func bcGetTransactionHeight(ic *interop.Context) error { - _, h, err := getTransactionAndHeight(ic.DAO, ic.VM) - if err != nil || !isTraceableBlock(ic, h) { - ic.VM.Estack().PushVal(-1) - return nil - } - ic.VM.Estack().PushVal(h) - return nil -} - // engineGetScriptContainer returns transaction or block that contains the script // being run. func engineGetScriptContainer(ic *interop.Context) error { var item stackitem.Item switch t := ic.Container.(type) { case *transaction.Transaction: - item = transactionToStackItem(t) + item = native.TransactionToStackItem(t) case *block.Block: - item = blockToStackItem(t) + item = native.BlockToStackItem(t) default: return errors.New("unknown script container") } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 9b574242e..2ada38325 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -29,166 +29,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestBCGetTransactionHeight(t *testing.T) { - v, tx, ic, chain := createVMAndTX(t) - defer chain.Close() - - for i := 0; i < 13; i++ { - require.NoError(t, chain.AddBlock(chain.newBlock())) - } - require.NoError(t, ic.DAO.StoreAsTransaction(tx, 13, nil)) - t.Run("good", func(t *testing.T) { - v.Estack().PushVal(tx.Hash().BytesBE()) - require.NoError(t, bcGetTransactionHeight(ic)) - require.Equal(t, big.NewInt(13), v.Estack().Pop().BigInt()) - }) - t.Run("bad", func(t *testing.T) { - h := tx.Hash() - h[0] ^= 0xFF - v.Estack().PushVal(h.BytesBE()) - require.NoError(t, bcGetTransactionHeight(ic)) - require.Equal(t, big.NewInt(-1), v.Estack().Pop().BigInt()) - }) -} - -func TestBCGetTransaction(t *testing.T) { - v, tx, context, chain := createVMAndTX(t) - defer chain.Close() - - t.Run("success", func(t *testing.T) { - require.NoError(t, context.DAO.StoreAsTransaction(tx, 0, nil)) - v.Estack().PushVal(tx.Hash().BytesBE()) - err := bcGetTransaction(context) - require.NoError(t, err) - - value := v.Estack().Pop().Value() - actual, ok := value.([]stackitem.Item) - require.True(t, ok) - require.Equal(t, 8, len(actual)) - require.Equal(t, tx.Hash().BytesBE(), actual[0].Value().([]byte)) - require.Equal(t, int64(tx.Version), actual[1].Value().(*big.Int).Int64()) - require.Equal(t, int64(tx.Nonce), actual[2].Value().(*big.Int).Int64()) - require.Equal(t, tx.Sender().BytesBE(), actual[3].Value().([]byte)) - require.Equal(t, int64(tx.SystemFee), actual[4].Value().(*big.Int).Int64()) - require.Equal(t, int64(tx.NetworkFee), actual[5].Value().(*big.Int).Int64()) - require.Equal(t, int64(tx.ValidUntilBlock), actual[6].Value().(*big.Int).Int64()) - require.Equal(t, tx.Script, actual[7].Value().([]byte)) - }) - - t.Run("isn't traceable", func(t *testing.T) { - require.NoError(t, context.DAO.StoreAsTransaction(tx, 1, nil)) - v.Estack().PushVal(tx.Hash().BytesBE()) - err := bcGetTransaction(context) - require.NoError(t, err) - - _, ok := v.Estack().Pop().Item().(stackitem.Null) - require.True(t, ok) - }) - - t.Run("bad hash", func(t *testing.T) { - require.NoError(t, context.DAO.StoreAsTransaction(tx, 1, nil)) - v.Estack().PushVal(tx.Hash().BytesLE()) - err := bcGetTransaction(context) - require.NoError(t, err) - - _, ok := v.Estack().Pop().Item().(stackitem.Null) - require.True(t, ok) - }) -} - -func TestBCGetTransactionFromBlock(t *testing.T) { - v, block, context, chain := createVMAndBlock(t) - defer chain.Close() - require.NoError(t, chain.AddBlock(chain.newBlock())) - require.NoError(t, context.DAO.StoreAsBlock(block, nil)) - - t.Run("success", func(t *testing.T) { - v.Estack().PushVal(0) - v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context) - require.NoError(t, err) - - value := v.Estack().Pop().Value() - actual, ok := value.([]byte) - require.True(t, ok) - require.Equal(t, block.Transactions[0].Hash().BytesBE(), actual) - }) - - t.Run("invalid block hash", func(t *testing.T) { - v.Estack().PushVal(0) - v.Estack().PushVal(block.Hash().BytesBE()[:10]) - err := bcGetTransactionFromBlock(context) - require.Error(t, err) - }) - - t.Run("isn't traceable", func(t *testing.T) { - block.Index = 2 - require.NoError(t, context.DAO.StoreAsBlock(block, nil)) - v.Estack().PushVal(0) - v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context) - require.NoError(t, err) - - _, ok := v.Estack().Pop().Item().(stackitem.Null) - require.True(t, ok) - }) - - t.Run("bad block hash", func(t *testing.T) { - block.Index = 1 - require.NoError(t, context.DAO.StoreAsBlock(block, nil)) - v.Estack().PushVal(0) - v.Estack().PushVal(block.Hash().BytesLE()) - err := bcGetTransactionFromBlock(context) - require.NoError(t, err) - - _, ok := v.Estack().Pop().Item().(stackitem.Null) - require.True(t, ok) - }) - - t.Run("bad transaction index", func(t *testing.T) { - require.NoError(t, context.DAO.StoreAsBlock(block, nil)) - v.Estack().PushVal(1) - v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context) - require.Error(t, err) - }) -} - -func TestBCGetBlock(t *testing.T) { - v, context, chain := createVM(t) - defer chain.Close() - block := chain.newBlock() - require.NoError(t, chain.AddBlock(block)) - - t.Run("success", func(t *testing.T) { - v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetBlock(context) - require.NoError(t, err) - - value := v.Estack().Pop().Value() - actual, ok := value.([]stackitem.Item) - require.True(t, ok) - require.Equal(t, 8, len(actual)) - require.Equal(t, block.Hash().BytesBE(), actual[0].Value().([]byte)) - require.Equal(t, int64(block.Version), actual[1].Value().(*big.Int).Int64()) - require.Equal(t, block.PrevHash.BytesBE(), actual[2].Value().([]byte)) - require.Equal(t, block.MerkleRoot.BytesBE(), actual[3].Value().([]byte)) - require.Equal(t, int64(block.Timestamp), actual[4].Value().(*big.Int).Int64()) - require.Equal(t, int64(block.Index), actual[5].Value().(*big.Int).Int64()) - require.Equal(t, block.NextConsensus.BytesBE(), actual[6].Value().([]byte)) - require.Equal(t, int64(len(block.Transactions)), actual[7].Value().(*big.Int).Int64()) - }) - - t.Run("bad hash", func(t *testing.T) { - v.Estack().PushVal(block.Hash().BytesLE()) - err := bcGetTransaction(context) - require.NoError(t, err) - - _, ok := v.Estack().Pop().Item().(stackitem.Null) - require.True(t, ok) - }) -} - func TestContractIsStandard(t *testing.T) { v, ic, chain := createVM(t) defer chain.Close() diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 08dccd64d..7e90fbc36 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -39,16 +39,6 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 1 << 12, ParamCount: 2}, {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 1 << 16, - RequiredFlags: callflag.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 1 << 4, - RequiredFlags: callflag.ReadStates}, - {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1 << 15, - RequiredFlags: callflag.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetTransactionFromBlock, Func: bcGetTransactionFromBlock, Price: 1 << 15, - RequiredFlags: callflag.ReadStates, ParamCount: 2}, - {Name: interopnames.SystemBlockchainGetTransactionHeight, Func: bcGetTransactionHeight, Price: 1 << 15, - RequiredFlags: callflag.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: callflag.AllowCall, ParamCount: 4}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1}, diff --git a/pkg/core/native/compatibility_test.go b/pkg/core/native/compatibility_test.go index de96eb982..f581264df 100644 --- a/pkg/core/native/compatibility_test.go +++ b/pkg/core/native/compatibility_test.go @@ -9,11 +9,12 @@ import ( // Compatibility test. hashes are taken directly from C# node. func TestNativeHashes(t *testing.T) { require.Equal(t, "a501d7d7d10983673b61b7a2d3a813b36f9f0e43", newManagement().Hash.StringLE()) - require.Equal(t, "f617baca689d1abddedda7c3b80675c4ac21e932", newNEO().Hash.StringLE()) - require.Equal(t, "75844530eb44f4715a42950bb59b4d7ace0b2f3d", newGAS().Hash.StringLE()) - require.Equal(t, "e21a28cfc1e662e152f668c86198141cc17b6c37", newPolicy().Hash.StringLE()) - require.Equal(t, "69b1909aaa14143b0624ba0d61d5cd3b8b67529d", newDesignate(false).Hash.StringLE()) - require.Equal(t, "b82bbf650f963dbf71577d10ea4077e711a13e7b", newOracle().Hash.StringLE()) + require.Equal(t, "971d69c6dd10ce88e7dfffec1dc603c6125a8764", newLedger().Hash.StringLE()) + require.Equal(t, "f61eebf573ea36593fd43aa150c055ad7906ab83", newNEO().Hash.StringLE()) + require.Equal(t, "70e2301955bf1e74cbb31d18c2f96972abadb328", newGAS().Hash.StringLE()) + require.Equal(t, "79bcd398505eb779df6e67e4be6c14cded08e2f2", newPolicy().Hash.StringLE()) + require.Equal(t, "597b1471bbce497b7809e2c8f10db67050008b02", newDesignate(false).Hash.StringLE()) + require.Equal(t, "8dc0e742cbdfdeda51ff8a8b78d46829144c80ee", newOracle().Hash.StringLE()) // Not yet a part of NEO. //require.Equal(t, "", newNotary().Hash.StringLE()()) } diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 567042446..7a0479144 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -16,6 +16,7 @@ const reservedContractID = -100 // Contracts is a set of registered native contracts. type Contracts struct { Management *Management + Ledger *Ledger NEO *NEO GAS *GAS Policy *Policy @@ -60,6 +61,10 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs.Management = mgmt cs.Contracts = append(cs.Contracts, mgmt) + ledger := newLedger() + cs.Ledger = ledger + cs.Contracts = append(cs.Contracts, ledger) + gas := newGAS() neo := newNEO() neo.GAS = gas diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 768d5b177..07680ce7a 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -48,7 +48,7 @@ type roleData struct { } const ( - designateContractID = -5 + designateContractID = -6 // maxNodeCount is the maximum number of nodes to set the role for. maxNodeCount = 32 diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go new file mode 100644 index 000000000..b521238ce --- /dev/null +++ b/pkg/core/native/ledger.go @@ -0,0 +1,226 @@ +package native + +import ( + "fmt" + "math" + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" + "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Ledger provides an interface to blocks/transactions storage for smart +// contracts. It's not a part of the proper chain's state, so it's just a +// proxy between regular Blockchain/DAO interface and smart contracts. +type Ledger struct { + interop.ContractMD +} + +const ( + ledgerContractID = -2 + + prefixBlockHash = 9 + prefixCurrentBlock = 12 + prefixBlock = 5 + prefixTransaction = 11 +) + +// newLedger creates new Ledger native contract. +func newLedger() *Ledger { + var l = &Ledger{ + ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID), + } + desc := newDescriptor("currentHash", smartcontract.Hash256Type) + md := newMethodAndPrice(l.currentHash, 1000000, callflag.ReadStates) + l.AddMethod(md, desc) + + desc = newDescriptor("currentIndex", smartcontract.IntegerType) + md = newMethodAndPrice(l.currentIndex, 1000000, callflag.ReadStates) + l.AddMethod(md, desc) + + desc = newDescriptor("getBlock", smartcontract.ArrayType, + manifest.NewParameter("indexOrHash", smartcontract.ByteArrayType)) + md = newMethodAndPrice(l.getBlock, 1000000, callflag.ReadStates) + l.AddMethod(md, desc) + + desc = newDescriptor("getTransaction", smartcontract.ArrayType, + manifest.NewParameter("hash", smartcontract.ByteArrayType)) + md = newMethodAndPrice(l.getTransaction, 1000000, callflag.ReadStates) + l.AddMethod(md, desc) + + desc = newDescriptor("getTransactionHeight", smartcontract.IntegerType, + manifest.NewParameter("hash", smartcontract.ByteArrayType)) + md = newMethodAndPrice(l.getTransactionHeight, 1000000, callflag.ReadStates) + l.AddMethod(md, desc) + + desc = newDescriptor("getTransactionFromBlock", smartcontract.ArrayType, + manifest.NewParameter("blockIndexOrHash", smartcontract.ByteArrayType), + manifest.NewParameter("txIndex", smartcontract.IntegerType)) + md = newMethodAndPrice(l.getTransactionFromBlock, 2000000, callflag.ReadStates) + l.AddMethod(md, desc) + + return l +} + +// Metadata implements Contract interface. +func (l *Ledger) Metadata() *interop.ContractMD { + return &l.ContractMD +} + +// Initialize implements Contract interface. +func (l *Ledger) Initialize(ic *interop.Context) error { + return nil +} + +// OnPersist implements Contract interface. +func (l *Ledger) OnPersist(ic *interop.Context) error { + // Actual block/tx processing is done in Blockchain.storeBlock(). + // Even though C# node add them to storage here, they're not + // accessible to smart contracts (see isTraceableBlock()), thus + // the end effect is the same. + return nil +} + +// PostPersist implements Contract interface. +func (l *Ledger) PostPersist(ic *interop.Context) error { + return nil // Actual block/tx processing is done in Blockchain.storeBlock(). +} + +// currentHash implements currentHash SC method. +func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.Make(ic.Chain.CurrentBlockHash().BytesBE()) +} + +// currentIndex implements currentIndex SC method. +func (l *Ledger) currentIndex(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.Make(ic.Chain.BlockHeight()) +} + +// getBlock implements getBlock SC method. +func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item { + hash := getBlockHashFromItem(ic.Chain, params[0]) + block, err := ic.Chain.GetBlock(hash) + if err != nil || !isTraceableBlock(ic.Chain, block.Index) { + return stackitem.Null{} + } + return BlockToStackItem(block) +} + +// getTransaction returns transaction to the SC. +func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item { + tx, h, err := getTransactionAndHeight(ic.DAO, params[0]) + if err != nil || !isTraceableBlock(ic.Chain, h) { + return stackitem.Null{} + } + return TransactionToStackItem(tx) +} + +// getTransactionHeight returns transaction height to the SC. +func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item { + _, h, err := getTransactionAndHeight(ic.DAO, params[0]) + if err != nil || !isTraceableBlock(ic.Chain, h) { + return stackitem.Make(-1) + } + return stackitem.Make(h) +} + +// getTransactionFromBlock returns transaction with the given index from the +// block with height or hash specified. +func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item { + hash := getBlockHashFromItem(ic.Chain, params[0]) + index := toUint32(params[1]) + block, err := ic.Chain.GetBlock(hash) + if err != nil || !isTraceableBlock(ic.Chain, block.Index) { + return stackitem.Null{} + } + if index >= uint32(len(block.Transactions)) { + panic("wrong transaction index") + } + return TransactionToStackItem(block.Transactions[index]) +} + +// isTraceableBlock defines whether we're able to give information about +// the block with index specified. +func isTraceableBlock(bc blockchainer.Blockchainer, index uint32) bool { + height := bc.BlockHeight() + MaxTraceableBlocks := bc.GetConfig().MaxTraceableBlocks + return index <= height && index+MaxTraceableBlocks > height +} + +// getBlockHashFromItem converts given stackitem.Item to block hash using given +// Blockchainer if needed. Interop functions accept both block numbers and +// block hashes as parameters, thus this function is needed. It's supposed to +// be called within VM context, so it panics if anything goes wrong. +func getBlockHashFromItem(bc blockchainer.Blockchainer, item stackitem.Item) util.Uint256 { + bigindex, err := item.TryInteger() + if err == nil && bigindex.IsInt64() { + index := bigindex.Int64() + if index < 0 || index > math.MaxUint32 { + panic("bad block index") + } + if uint32(index) > bc.BlockHeight() { + panic(fmt.Errorf("no block with index %d", index)) + } + return bc.GetHeaderHash(int(index)) + } + bytes, err := item.TryBytes() + if err != nil { + panic(err) + } + hash, err := util.Uint256DecodeBytesBE(bytes) + if err != nil { + panic(err) + } + return hash +} + +// getTransactionAndHeight returns transaction and its height if it's present +// on the chain. It panics if anything goes wrong. +func getTransactionAndHeight(cd *dao.Cached, item stackitem.Item) (*transaction.Transaction, uint32, error) { + hashbytes, err := item.TryBytes() + if err != nil { + panic(err) + } + hash, err := util.Uint256DecodeBytesBE(hashbytes) + if err != nil { + panic(err) + } + return cd.GetTransaction(hash) +} + +// BlockToStackItem converts block.Block to stackitem.Item +func BlockToStackItem(b *block.Block) stackitem.Item { + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(b.Hash().BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(b.Version))), + stackitem.NewByteArray(b.PrevHash.BytesBE()), + stackitem.NewByteArray(b.MerkleRoot.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))), + stackitem.NewBigInteger(big.NewInt(int64(b.Index))), + stackitem.NewByteArray(b.NextConsensus.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))), + }) +} + +// TransactionToStackItem converts transaction.Transaction to stackitem.Item +func TransactionToStackItem(t *transaction.Transaction) stackitem.Item { + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(t.Hash().BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(t.Version))), + stackitem.NewBigInteger(big.NewInt(int64(t.Nonce))), + stackitem.NewByteArray(t.Sender().BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(t.SystemFee))), + stackitem.NewBigInteger(big.NewInt(int64(t.NetworkFee))), + stackitem.NewBigInteger(big.NewInt(int64(t.ValidUntilBlock))), + stackitem.NewByteArray(t.Script), + }) +} diff --git a/pkg/core/native/name_service.go b/pkg/core/native/name_service.go index 0690cb414..cfc3480ca 100644 --- a/pkg/core/native/name_service.go +++ b/pkg/core/native/name_service.go @@ -54,7 +54,7 @@ const ( ) const ( - nameServiceID = -7 + nameServiceID = -8 prefixRoots = 10 prefixDomainPrice = 22 diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 414cb3cdd..00111cea1 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -18,7 +18,7 @@ type GAS struct { NEO *NEO } -const gasContractID = -3 +const gasContractID = -4 // GASFactor is a divisor for finding GAS integral value. const GASFactor = NEOTotalSupply diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index d39100f83..614300ab2 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -51,7 +51,7 @@ type NEO struct { } const ( - neoContractID = -2 + neoContractID = -3 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 // prefixCandidate is a prefix used to store validator's data. diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index eb007cbbc..0f37e5ba8 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -3,6 +3,7 @@ package nativenames // Names of all native contracts. const ( Management = "ContractManagement" + Ledger = "LedgerContract" Neo = "NeoToken" Gas = "GasToken" Policy = "PolicyContract" diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index ed775d7d4..ce48494f2 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -46,7 +46,7 @@ type Oracle struct { } const ( - oracleContractID = -6 + oracleContractID = -7 maxURLLength = 256 maxFilterLength = 128 maxCallbackLength = 32 diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 4afdef5ce..a9f0de9c7 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -21,7 +21,7 @@ import ( ) const ( - policyContractID = -4 + policyContractID = -5 defaultMaxBlockSize = 1024 * 256 defaultMaxTransactionsPerBlock = 512 diff --git a/pkg/core/native_ledger_test.go b/pkg/core/native_ledger_test.go new file mode 100644 index 000000000..cb25ccf14 --- /dev/null +++ b/pkg/core/native_ledger_test.go @@ -0,0 +1,177 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestLedgerGetTransactionHeight(t *testing.T) { + _, tx, _, chain := createVMAndTX(t) + defer chain.Close() + + ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash + + for i := 0; i < 13; i++ { + require.NoError(t, chain.AddBlock(chain.newBlock())) + } + require.NoError(t, chain.dao.StoreAsTransaction(tx, 13, nil)) + t.Run("good", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", tx.Hash().BytesBE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Make(13)) + }) + t.Run("bad", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", tx.Hash().BytesLE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Make(-1)) + }) + t.Run("not a hash", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", []byte{1}) + require.NoError(t, err) + checkFAULTState(t, res) + }) +} + +func TestLedgerGetTransaction(t *testing.T) { + _, tx, _, chain := createVMAndTX(t) + defer chain.Close() + ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash + + t.Run("success", func(t *testing.T) { + require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil)) + + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE()) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState, res.FaultException) + require.Equal(t, 1, len(res.Stack)) + value := res.Stack[0].Value() + + actual, ok := value.([]stackitem.Item) + require.True(t, ok) + require.Equal(t, 8, len(actual)) + require.Equal(t, tx.Hash().BytesBE(), actual[0].Value().([]byte)) + require.Equal(t, int64(tx.Version), actual[1].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.Nonce), actual[2].Value().(*big.Int).Int64()) + require.Equal(t, tx.Sender().BytesBE(), actual[3].Value().([]byte)) + require.Equal(t, int64(tx.SystemFee), actual[4].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.NetworkFee), actual[5].Value().(*big.Int).Int64()) + require.Equal(t, int64(tx.ValidUntilBlock), actual[6].Value().(*big.Int).Int64()) + require.Equal(t, tx.Script, actual[7].Value().([]byte)) + }) + + t.Run("isn't traceable", func(t *testing.T) { + require.NoError(t, chain.dao.StoreAsTransaction(tx, 2, nil)) // block 1 is added above + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) + t.Run("bad hash", func(t *testing.T) { + require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil)) + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesLE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) +} + +func TestLedgerGetTransactionFromBlock(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash + + res, err := invokeContractMethod(chain, 100000000, ledger, "currentIndex") // adds a block + require.NoError(t, err) + checkResult(t, res, stackitem.Make(0)) + bhash := chain.GetHeaderHash(1) + b, err := chain.GetBlock(bhash) + require.NoError(t, err) + + t.Run("success", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(0)) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState, res.FaultException) + require.Equal(t, 1, len(res.Stack)) + value := res.Stack[0].Value() + + actual, ok := value.([]stackitem.Item) + require.True(t, ok) + require.Equal(t, b.Transactions[0].Hash().BytesBE(), actual[0].Value().([]byte)) + }) + t.Run("bad transaction index", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(1)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid block hash (>int64)", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE()[:10], int64(0)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid block hash (int64)", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE()[:6], int64(0)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("bad block hash", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesLE(), int64(0)) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) + t.Run("isn't traceable", func(t *testing.T) { + b.Index = chain.BlockHeight() + 1 + require.NoError(t, chain.dao.StoreAsBlock(b, nil)) + res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(0)) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) +} + +func TestLedgerGetBlock(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash + + bhash := chain.GetHeaderHash(0) + res, err := invokeContractMethod(chain, 100000000, ledger, "currentHash") // adds a block + require.NoError(t, err) + checkResult(t, res, stackitem.Make(bhash.BytesBE())) + bhash = chain.GetHeaderHash(1) + b, err := chain.GetBlock(bhash) + require.NoError(t, err) + + t.Run("success", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesBE()) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState, res.FaultException) + require.Equal(t, 1, len(res.Stack)) + value := res.Stack[0].Value() + + actual, ok := value.([]stackitem.Item) + require.True(t, ok) + require.Equal(t, 8, len(actual)) + require.Equal(t, b.Hash().BytesBE(), actual[0].Value().([]byte)) + require.Equal(t, int64(b.Version), actual[1].Value().(*big.Int).Int64()) + require.Equal(t, b.PrevHash.BytesBE(), actual[2].Value().([]byte)) + require.Equal(t, b.MerkleRoot.BytesBE(), actual[3].Value().([]byte)) + require.Equal(t, int64(b.Timestamp), actual[4].Value().(*big.Int).Int64()) + require.Equal(t, int64(b.Index), actual[5].Value().(*big.Int).Int64()) + require.Equal(t, b.NextConsensus.BytesBE(), actual[6].Value().([]byte)) + require.Equal(t, int64(len(b.Transactions)), actual[7].Value().(*big.Int).Int64()) + }) + t.Run("bad hash", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesLE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) + t.Run("isn't traceable", func(t *testing.T) { + b.Index = chain.BlockHeight() + 1 + require.NoError(t, chain.dao.StoreAsBlock(b, nil)) + res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesBE()) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) +} diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go deleted file mode 100644 index fffd667fa..000000000 --- a/pkg/interop/blockchain/blockchain.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Package blockchain provides functions to access various blockchain data. -*/ -package blockchain - -import ( - "github.com/nspcc-dev/neo-go/pkg/interop" -) - -// Transaction represents a NEO transaction. It's similar to Transaction class -// in Neo .net framework. -type Transaction struct { - // Hash represents the hash (256 bit BE value in a 32 byte slice) of the - // given transaction (which also is its ID). - Hash interop.Hash256 - // Version represents the transaction version. - Version int - // Nonce is a random number to avoid hash collision. - Nonce int - // Sender represents the sender (160 bit BE value in a 20 byte slice) of the - // given Transaction. - Sender interop.Hash160 - // SysFee represents fee to be burned. - SysFee int - // NetFee represents fee to be distributed to consensus nodes. - NetFee int - // ValidUntilBlock is the maximum blockchain height exceeding which - // transaction should fail verification. - ValidUntilBlock int - // Script represents code to run in NeoVM for this transaction. - Script []byte -} - -// Block represents a NEO block, it's a data structure that you can get -// block-related data from. It's similar to the Block class in the Neo .net -// framework. To use it you need to get it via GetBlock function call. -type Block struct { - // Hash represents the hash (256 bit BE value in a 32 byte slice) of the - // given block. - Hash interop.Hash256 - // Version of the block. - Version int - // PrevHash represents the hash (256 bit BE value in a 32 byte slice) of the - // previous block. - PrevHash interop.Hash256 - // MerkleRoot represents the root hash (256 bit BE value in a 32 byte slice) - // of a transaction list. - MerkleRoot interop.Hash256 - // Timestamp represents millisecond-precision block timestamp. - Timestamp int - // Index represents the height of the block. - Index int - // NextConsensus represents contract address of the next miner (160 bit BE - // value in a 20 byte slice). - NextConsensus interop.Hash160 - // TransactionsLength represents the length of block's transactions array. - TransactionsLength int -} - -// GetHeight returns current block height (index of the last accepted block). -// Note that when transaction is being run as a part of new block this block is -// considered as not yet accepted (persisted) and thus you'll get an index of -// the previous (already accepted) block. This function uses -// `System.Blockchain.GetHeight` syscall. -func GetHeight() int { - return 0 -} - -// GetBlock returns block found by the given hash or index (with the same -// encoding as for GetHeader). This function uses `System.Blockchain.GetBlock` -// syscall. -func GetBlock(heightOrHash interface{}) *Block { - return &Block{} -} - -// GetTransaction returns transaction found by the given hash (256 bit in BE -// format represented as a slice of 32 bytes). This function uses -// `System.Blockchain.GetTransaction` syscall. -func GetTransaction(hash interop.Hash256) *Transaction { - return &Transaction{} -} - -// GetTransactionFromBlock returns transaction hash (256 bit in BE format -// represented as a slice of 32 bytes) from the block found by the given hash or -// index (with the same encoding as for GetHeader) by its index. This -// function uses `System.Blockchain.GetTransactionFromBlock` syscall. -func GetTransactionFromBlock(heightOrHash interface{}, index int) interop.Hash256 { - return nil -} - -// GetTransactionHeight returns transaction's height (index of the block that -// includes it) by the given ID (256 bit in BE format represented as a slice of -// 32 bytes). This function uses `System.Blockchain.GetTransactionHeight` syscall. -func GetTransactionHeight(hash interop.Hash256) int { - return 0 -} diff --git a/pkg/interop/runtime/engine.go b/pkg/interop/runtime/engine.go index 64e0b7b66..a8180b457 100644 --- a/pkg/interop/runtime/engine.go +++ b/pkg/interop/runtime/engine.go @@ -2,15 +2,38 @@ package runtime import ( "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" ) +// Transaction represents a NEO transaction. It's similar to Transaction class +// in Neo .net framework. +type Transaction struct { + // Hash represents the hash (256 bit BE value in a 32 byte slice) of the + // given transaction (which also is its ID). + Hash interop.Hash256 + // Version represents the transaction version. + Version int + // Nonce is a random number to avoid hash collision. + Nonce int + // Sender represents the sender (160 bit BE value in a 20 byte slice) of the + // given Transaction. + Sender interop.Hash160 + // SysFee represents fee to be burned. + SysFee int + // NetFee represents fee to be distributed to consensus nodes. + NetFee int + // ValidUntilBlock is the maximum blockchain height exceeding which + // transaction should fail verification. + ValidUntilBlock int + // Script represents code to run in NeoVM for this transaction. + Script []byte +} + // GetScriptContainer returns the transaction that initially triggered current // execution context. It never changes in a single execution, no matter how deep // this execution goes. This function uses // `System.Runtime.GetScriptContainer` syscall. -func GetScriptContainer() *blockchain.Transaction { - return &blockchain.Transaction{} +func GetScriptContainer() *Transaction { + return &Transaction{} } // GetExecutingScriptHash returns script hash (160 bit in BE form represented diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index a2da8e512..b99011ef4 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -28,9 +28,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" - rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" "github.com/nspcc-dev/neo-go/pkg/rpc/response" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" + rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -59,7 +59,7 @@ type rpcTestCase struct { } const testContractHash = "c6436aab21ebd15279b85af8d7b5808d38455b0a" -const deploymentTxHash = "e6ffce4533231c4efdea9a65c7abc0e7073d96a4ebc66f402db3a84b6f8939ef" +const deploymentTxHash = "9a9d6b0876d1e6cfd68efadd0facaaba7e07efbe7b24282d094a0893645581f3" const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "03ffc0897543b9b709e0f8cab4a7682dae0ba943" @@ -182,7 +182,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, cs interface{}) { res, ok := cs.(*state.Contract) require.True(t, ok) - assert.Equal(t, int32(-4), res.ID) + assert.Equal(t, int32(-5), res.ID) }, }, { @@ -192,7 +192,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { name: "negative, bad ID", - params: `[-8]`, + params: `[-100]`, fail: true, }, { diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index ac9b113ac..d8320664d 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ