native: add Ledger contract, fix #1696
But don't change the way we process/store transactions and blocks. Effectively it's just an interface for smart contracts that replaces old syscalls. Transaction definition is moved temporarily to runtime package and Block definition is removed (till we solve #1691 properly).
This commit is contained in:
parent
641896b2fb
commit
ac527650eb
24 changed files with 466 additions and 445 deletions
BIN
cli/testdata/chain50x2.acc
vendored
BIN
cli/testdata/chain50x2.acc
vendored
Binary file not shown.
|
@ -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" {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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()())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
226
pkg/core/native/ledger.go
Normal file
226
pkg/core/native/ledger.go
Normal file
|
@ -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),
|
||||
})
|
||||
}
|
|
@ -54,7 +54,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
nameServiceID = -7
|
||||
nameServiceID = -8
|
||||
|
||||
prefixRoots = 10
|
||||
prefixDomainPrice = 22
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -3,6 +3,7 @@ package nativenames
|
|||
// Names of all native contracts.
|
||||
const (
|
||||
Management = "ContractManagement"
|
||||
Ledger = "LedgerContract"
|
||||
Neo = "NeoToken"
|
||||
Gas = "GasToken"
|
||||
Policy = "PolicyContract"
|
||||
|
|
|
@ -46,7 +46,7 @@ type Oracle struct {
|
|||
}
|
||||
|
||||
const (
|
||||
oracleContractID = -6
|
||||
oracleContractID = -7
|
||||
maxURLLength = 256
|
||||
maxFilterLength = 128
|
||||
maxCallbackLength = 32
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
policyContractID = -4
|
||||
policyContractID = -5
|
||||
|
||||
defaultMaxBlockSize = 1024 * 256
|
||||
defaultMaxTransactionsPerBlock = 512
|
||||
|
|
177
pkg/core/native_ledger_test.go
Normal file
177
pkg/core/native_ledger_test.go
Normal file
|
@ -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{})
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue