Merge pull request #1705 from nspcc-dev/ledger-contract

native: add Ledger contract
This commit is contained in:
Roman Khimov 2021-02-04 13:33:46 +03:00 committed by GitHub
commit d14ab4ba69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 472 additions and 446 deletions

Binary file not shown.

View file

@ -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

View file

@ -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" {

View file

@ -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 {}

View file

@ -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,

View file

@ -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 {

View file

@ -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) {

View file

@ -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,

View file

@ -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")
}

View file

@ -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()

View file

@ -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},

View file

@ -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()())
}

View file

@ -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

View file

@ -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
View 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),
})
}

View file

@ -54,7 +54,7 @@ const (
)
const (
nameServiceID = -7
nameServiceID = -8
prefixRoots = 10
prefixDomainPrice = 22

View file

@ -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

View file

@ -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.

View file

@ -3,6 +3,7 @@ package nativenames
// Names of all native contracts.
const (
Management = "ContractManagement"
Ledger = "LedgerContract"
Neo = "NeoToken"
Gas = "GasToken"
Policy = "PolicyContract"

View file

@ -46,7 +46,7 @@ type Oracle struct {
}
const (
oracleContractID = -6
oracleContractID = -7
maxURLLength = 256
maxFilterLength = 128
maxCallbackLength = 32

View file

@ -21,7 +21,7 @@ import (
)
const (
policyContractID = -4
policyContractID = -5
defaultMaxBlockSize = 1024 * 256
defaultMaxTransactionsPerBlock = 512

View 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{})
})
}

View file

@ -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
}

View file

@ -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

View file

@ -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,
},
{

Binary file not shown.