forked from TrueCloudLab/neoneo-go
Merge pull request #1705 from nspcc-dev/ledger-contract
native: add Ledger contract
This commit is contained in:
commit
d14ab4ba69
26 changed files with 472 additions and 446 deletions
BIN
cli/testdata/chain50x2.acc
vendored
BIN
cli/testdata/chain50x2.acc
vendored
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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