forked from TrueCloudLab/neoneo-go
Merge pull request #2431 from nspcc-dev/rpc/historicall
rpc: support historic calls
This commit is contained in:
commit
83f8ecf2e0
41 changed files with 2055 additions and 591 deletions
22
docs/rpc.md
22
docs/rpc.md
|
@ -168,6 +168,28 @@ block. It can be removed in future versions, but at the moment you can use it
|
||||||
to see how much GAS is burned with particular block (because system fees are
|
to see how much GAS is burned with particular block (because system fees are
|
||||||
burned).
|
burned).
|
||||||
|
|
||||||
|
#### `invokecontractverifyhistoric`, `invokefunctionhistoric` and `invokescripthistoric` calls
|
||||||
|
|
||||||
|
These methods provide the ability of *historical* calls and accept block hash or
|
||||||
|
block index or stateroot hash as the first parameter and the list of parameters
|
||||||
|
that is the same as of `invokecontractverify`, `invokefunction` and
|
||||||
|
`invokescript` correspondingly. The historical call assumes that the contracts'
|
||||||
|
storage state has all its values got from MPT with the specified stateroot and
|
||||||
|
the transaction will be invoked using interop context with block of the specified
|
||||||
|
height. This allows to perform test invocation using the specified past chain
|
||||||
|
state. These methods may be useful for debugging purposes.
|
||||||
|
|
||||||
|
Behavior note: any historical RPC call need the historical chain state to be
|
||||||
|
presented in the node storage, thus if the node keeps only latest MPT state
|
||||||
|
the historical call can not be handled properly.The historical calls only
|
||||||
|
guaranteed to correctly work on archival node that stores all MPT data. If a
|
||||||
|
node keeps the number of latest states and has the GC on (this setting
|
||||||
|
corresponds to the `RemoveUntraceableBlocks` set to `true`), then the behaviour
|
||||||
|
of historical RPC call is undefined. GC can always kick some data out of the
|
||||||
|
storage while the historical call is executing, thus keep in mind that the call
|
||||||
|
can be processed with `RemoveUntraceableBlocks` only with limitations on
|
||||||
|
available data.
|
||||||
|
|
||||||
#### `submitnotaryrequest` call
|
#### `submitnotaryrequest` call
|
||||||
|
|
||||||
This method can be used on P2P Notary enabled networks to submit new notary
|
This method can be used on P2P Notary enabled networks to submit new notary
|
||||||
|
|
|
@ -201,7 +201,8 @@ func TestAppCall(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fc := fakechain.NewFakeChain()
|
fc := fakechain.NewFakeChain()
|
||||||
ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t))
|
ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false),
|
||||||
|
interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t))
|
||||||
|
|
||||||
t.Run("valid script", func(t *testing.T) {
|
t.Run("valid script", func(t *testing.T) {
|
||||||
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
||||||
|
|
|
@ -76,7 +76,7 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
|
|
||||||
func initBasicChain(t *testing.T, e *neotest.Executor) {
|
func initBasicChain(t *testing.T, e *neotest.Executor) {
|
||||||
if !e.Chain.GetConfig().P2PSigExtensions {
|
if !e.Chain.GetConfig().P2PSigExtensions {
|
||||||
t.Fatal("P2PSitExtensions should be enabled to init basic chain")
|
t.Fatal("P2PSigExtensions should be enabled to init basic chain")
|
||||||
}
|
}
|
||||||
|
|
||||||
const neoAmount = 99999000
|
const neoAmount = 99999000
|
||||||
|
|
|
@ -433,14 +433,9 @@ func (bc *Blockchain) init() error {
|
||||||
return fmt.Errorf("can't init MPT at height %d: %w", bHeight, err)
|
return fmt.Errorf("can't init MPT at height %d: %w", bHeight, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bc.contracts.NEO.InitializeCache(bc, bc.dao)
|
err = bc.initializeNativeCache(bc.blockHeight, bc.dao)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't init cache for NEO native contract: %w", err)
|
return fmt.Errorf("can't init natives cache: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
err = bc.contracts.Management.InitializeCache(bc.dao)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check autogenerated native contracts' manifests and NEFs against the stored ones.
|
// Check autogenerated native contracts' manifests and NEFs against the stored ones.
|
||||||
|
@ -575,15 +570,10 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error
|
||||||
Root: block.PrevStateRoot,
|
Root: block.PrevStateRoot,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = bc.contracts.NEO.InitializeCache(bc, bc.dao)
|
err = bc.initializeNativeCache(block.Index, bc.dao)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't init cache for NEO native contract: %w", err)
|
return fmt.Errorf("failed to initialize natives cache: %w", err)
|
||||||
}
|
}
|
||||||
err = bc.contracts.Management.InitializeCache(bc.dao)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
|
||||||
}
|
|
||||||
bc.contracts.Designate.InitializeCache()
|
|
||||||
|
|
||||||
if err := bc.updateExtensibleWhitelist(p); err != nil {
|
if err := bc.updateExtensibleWhitelist(p); err != nil {
|
||||||
return fmt.Errorf("failed to update extensible whitelist: %w", err)
|
return fmt.Errorf("failed to update extensible whitelist: %w", err)
|
||||||
|
@ -595,6 +585,33 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error {
|
||||||
|
err := bc.contracts.NEO.InitializeCache(blockHeight, d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for NEO native contract: %w", err)
|
||||||
|
}
|
||||||
|
err = bc.contracts.Management.InitializeCache(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
||||||
|
}
|
||||||
|
err = bc.contracts.Designate.InitializeCache(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for Designation native contract: %w", err)
|
||||||
|
}
|
||||||
|
bc.contracts.Oracle.InitializeCache(d)
|
||||||
|
if bc.P2PSigExtensionsEnabled() {
|
||||||
|
err = bc.contracts.Notary.InitializeCache(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for Notary native contract: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = bc.contracts.Policy.InitializeCache(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for Policy native contract: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Run runs chain loop, it needs to be run as goroutine and executing it is
|
// Run runs chain loop, it needs to be run as goroutine and executing it is
|
||||||
// critical for correct Blockchain operation.
|
// critical for correct Blockchain operation.
|
||||||
func (bc *Blockchain) Run() {
|
func (bc *Blockchain) Run() {
|
||||||
|
@ -1220,14 +1237,14 @@ func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress()}
|
newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress(bc.dao)}
|
||||||
nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal()
|
nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao)
|
||||||
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals)
|
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newList = append(newList, hash.Hash160(script))
|
newList = append(newList, hash.Hash160(script))
|
||||||
bc.updateExtensibleList(&newList, bc.contracts.NEO.GetNextBlockValidatorsInternal())
|
bc.updateExtensibleList(&newList, bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao))
|
||||||
|
|
||||||
if len(stateVals) > 0 {
|
if len(stateVals) > 0 {
|
||||||
h, err := bc.contracts.Designate.GetLastDesignatedHash(bc.dao, noderoles.StateValidator)
|
h, err := bc.contracts.Designate.GetLastDesignatedHash(bc.dao, noderoles.StateValidator)
|
||||||
|
@ -1451,12 +1468,12 @@ func (bc *Blockchain) ForEachNEP11Transfer(acc util.Uint160, newestTimestamp uin
|
||||||
|
|
||||||
// GetNEP17Contracts returns the list of deployed NEP-17 contracts.
|
// GetNEP17Contracts returns the list of deployed NEP-17 contracts.
|
||||||
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 {
|
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 {
|
||||||
return bc.contracts.Management.GetNEP17Contracts()
|
return bc.contracts.Management.GetNEP17Contracts(bc.dao)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNEP11Contracts returns the list of deployed NEP-11 contracts.
|
// GetNEP11Contracts returns the list of deployed NEP-11 contracts.
|
||||||
func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 {
|
func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 {
|
||||||
return bc.contracts.Management.GetNEP11Contracts()
|
return bc.contracts.Management.GetNEP11Contracts(bc.dao)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokenLastUpdated returns a set of contract ids with the corresponding last updated
|
// GetTokenLastUpdated returns a set of contract ids with the corresponding last updated
|
||||||
|
@ -1823,7 +1840,7 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra
|
||||||
curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1)
|
curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1)
|
||||||
if oldVC == nil || oldVC != curVC {
|
if oldVC == nil || oldVC != curVC {
|
||||||
m := smartcontract.GetDefaultHonestNodeCount(curVC)
|
m := smartcontract.GetDefaultHonestNodeCount(curVC)
|
||||||
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal())
|
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao))
|
||||||
defaultWitness = transaction.Witness{
|
defaultWitness = transaction.Witness{
|
||||||
InvocationScript: make([]byte, 66*m),
|
InvocationScript: make([]byte, 66*m),
|
||||||
VerificationScript: verification,
|
VerificationScript: verification,
|
||||||
|
@ -1939,7 +1956,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = pool.Add(t, feer, data...)
|
err = pool.Add(t, feer, data...)
|
||||||
|
@ -1963,11 +1980,11 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction, isPartialTx bool) error {
|
func (bc *Blockchain) verifyTxAttributes(d *dao.Simple, tx *transaction.Transaction, isPartialTx bool) error {
|
||||||
for i := range tx.Attributes {
|
for i := range tx.Attributes {
|
||||||
switch attrType := tx.Attributes[i].Type; attrType {
|
switch attrType := tx.Attributes[i].Type; attrType {
|
||||||
case transaction.HighPriority:
|
case transaction.HighPriority:
|
||||||
h := bc.contracts.NEO.GetCommitteeAddress()
|
h := bc.contracts.NEO.GetCommitteeAddress(d)
|
||||||
if !tx.HasSigner(h) {
|
if !tx.HasSigner(h) {
|
||||||
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute)
|
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute)
|
||||||
}
|
}
|
||||||
|
@ -2061,7 +2078,7 @@ func (bc *Blockchain) IsTxStillRelevant(t *transaction.Transaction, txpool *memp
|
||||||
} else if txpool.HasConflicts(t, bc) {
|
} else if txpool.HasConflicts(t, bc) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := range t.Scripts {
|
for i := range t.Scripts {
|
||||||
|
@ -2119,19 +2136,19 @@ func (bc *Blockchain) PoolTxWithData(t *transaction.Transaction, data interface{
|
||||||
|
|
||||||
// GetCommittee returns the sorted list of public keys of nodes in committee.
|
// GetCommittee returns the sorted list of public keys of nodes in committee.
|
||||||
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
||||||
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
pubs := bc.contracts.NEO.GetCommitteeMembers(bc.dao)
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
return pubs, nil
|
return pubs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidators returns current validators.
|
// GetValidators returns current validators.
|
||||||
func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
|
func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
|
||||||
return bc.contracts.NEO.ComputeNextBlockValidators(bc, bc.dao)
|
return bc.contracts.NEO.ComputeNextBlockValidators(bc.blockHeight, bc.dao)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextBlockValidators returns next block validators.
|
// GetNextBlockValidators returns next block validators.
|
||||||
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
||||||
return bc.contracts.NEO.GetNextBlockValidatorsInternal(), nil
|
return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnrollments returns all registered validators.
|
// GetEnrollments returns all registered validators.
|
||||||
|
@ -2148,6 +2165,41 @@ func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *
|
||||||
return systemInterop
|
return systemInterop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTestHistoricVM returns an interop context with VM set up for a test run.
|
||||||
|
func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) {
|
||||||
|
if bc.config.KeepOnlyLatestState {
|
||||||
|
return nil, errors.New("only latest state is supported")
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return nil, errors.New("block is mandatory to produce test historic VM")
|
||||||
|
}
|
||||||
|
var mode = mpt.ModeAll
|
||||||
|
if bc.config.RemoveUntraceableBlocks {
|
||||||
|
if b.Index < bc.BlockHeight()-bc.config.MaxTraceableBlocks {
|
||||||
|
return nil, fmt.Errorf("state for height %d is outdated and removed from the storage", b.Index)
|
||||||
|
}
|
||||||
|
mode |= mpt.ModeGCFlag
|
||||||
|
}
|
||||||
|
sr, err := bc.stateRoot.GetStateRoot(b.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve stateroot for height %d: %w", b.Index, err)
|
||||||
|
}
|
||||||
|
s := mpt.NewTrieStore(sr.Root, mode, storage.NewPrivateMemCachedStore(bc.dao.Store))
|
||||||
|
dTrie := dao.NewSimple(s, bc.config.StateRootInHeader, bc.config.P2PSigExtensions)
|
||||||
|
dTrie.Version = bc.dao.Version
|
||||||
|
// Initialize native cache before passing DAO to interop context constructor, because
|
||||||
|
// the constructor will call BaseExecFee/StoragePrice policy methods on the passed DAO.
|
||||||
|
err = bc.initializeNativeCache(b.Index, dTrie)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize native cache backed by historic DAO: %w", err)
|
||||||
|
}
|
||||||
|
systemInterop := bc.newInteropContext(t, dTrie, b, tx)
|
||||||
|
vm := systemInterop.SpawnVM()
|
||||||
|
vm.SetPriceGetter(systemInterop.GetPrice)
|
||||||
|
vm.LoadToken = contract.LoadToken(systemInterop)
|
||||||
|
return systemInterop, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Various witness verification errors.
|
// Various witness verification errors.
|
||||||
var (
|
var (
|
||||||
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -329,7 +328,7 @@ func TestBlockchain_BaseExecFeeBaseStoragePrice_Compat(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
check := func(t *testing.T) {
|
check := func(t *testing.T) {
|
||||||
ic := bc.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), bc.config.StateRootInHeader, bc.config.P2PSigExtensions), bc.topBlock.Load().(*block.Block), nil)
|
ic := bc.newInteropContext(trigger.Application, bc.dao, bc.topBlock.Load().(*block.Block), nil)
|
||||||
require.Equal(t, bc.GetBaseExecFee(), ic.BaseExecFee())
|
require.Equal(t, bc.GetBaseExecFee(), ic.BaseExecFee())
|
||||||
require.Equal(t, bc.GetStoragePrice(), ic.BaseStorageFee())
|
require.Equal(t, bc.GetStoragePrice(), ic.BaseStorageFee())
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ type Blockchainer interface {
|
||||||
GetStateModule() StateRoot
|
GetStateModule() StateRoot
|
||||||
GetStorageItem(id int32, key []byte) state.StorageItem
|
GetStorageItem(id int32, key []byte) state.StorageItem
|
||||||
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context
|
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context
|
||||||
|
GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error)
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
SetOracle(service services.Oracle)
|
SetOracle(service services.Oracle)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
|
|
|
@ -15,4 +15,5 @@ type StateRoot interface {
|
||||||
GetState(root util.Uint256, key []byte) ([]byte, error)
|
GetState(root util.Uint256, key []byte) ([]byte, error)
|
||||||
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
||||||
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
||||||
|
GetLatestStateHeight(root util.Uint256) (uint32, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
iocore "io"
|
iocore "io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
@ -34,11 +35,28 @@ var (
|
||||||
type Simple struct {
|
type Simple struct {
|
||||||
Version Version
|
Version Version
|
||||||
Store *storage.MemCachedStore
|
Store *storage.MemCachedStore
|
||||||
|
|
||||||
|
nativeCacheLock sync.RWMutex
|
||||||
|
nativeCache map[int32]NativeContractCache
|
||||||
|
// nativeCachePS is the backend store that provides functionality to store
|
||||||
|
// and retrieve multi-tier native contract cache. The lowest Simple has its
|
||||||
|
// nativeCachePS set to nil.
|
||||||
|
nativeCachePS *Simple
|
||||||
|
|
||||||
private bool
|
private bool
|
||||||
keyBuf []byte
|
keyBuf []byte
|
||||||
dataBuf *io.BufBinWriter
|
dataBuf *io.BufBinWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NativeContractCache is an interface representing cache for a native contract.
|
||||||
|
// Cache can be copied to create a wrapper around current DAO layer. Wrapped cache
|
||||||
|
// can be persisted to the underlying DAO native cache.
|
||||||
|
type NativeContractCache interface {
|
||||||
|
// Copy returns a copy of native cache item that can safely be changed within
|
||||||
|
// the subsequent DAO operations.
|
||||||
|
Copy() NativeContractCache
|
||||||
|
}
|
||||||
|
|
||||||
// NewSimple creates new simple dao using provided backend store.
|
// NewSimple creates new simple dao using provided backend store.
|
||||||
func NewSimple(backend storage.Store, stateRootInHeader bool, p2pSigExtensions bool) *Simple {
|
func NewSimple(backend storage.Store, stateRootInHeader bool, p2pSigExtensions bool) *Simple {
|
||||||
st := storage.NewMemCachedStore(backend)
|
st := storage.NewMemCachedStore(backend)
|
||||||
|
@ -52,7 +70,8 @@ func newSimple(st *storage.MemCachedStore, stateRootInHeader bool, p2pSigExtensi
|
||||||
StateRootInHeader: stateRootInHeader,
|
StateRootInHeader: stateRootInHeader,
|
||||||
P2PSigExtensions: p2pSigExtensions,
|
P2PSigExtensions: p2pSigExtensions,
|
||||||
},
|
},
|
||||||
Store: st,
|
Store: st,
|
||||||
|
nativeCache: make(map[int32]NativeContractCache),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,16 +85,27 @@ func (dao *Simple) GetBatch() *storage.MemBatch {
|
||||||
func (dao *Simple) GetWrapped() *Simple {
|
func (dao *Simple) GetWrapped() *Simple {
|
||||||
d := NewSimple(dao.Store, dao.Version.StateRootInHeader, dao.Version.P2PSigExtensions)
|
d := NewSimple(dao.Store, dao.Version.StateRootInHeader, dao.Version.P2PSigExtensions)
|
||||||
d.Version = dao.Version
|
d.Version = dao.Version
|
||||||
|
d.nativeCachePS = dao
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrivate returns new DAO instance with another layer of private
|
// GetPrivate returns new DAO instance with another layer of private
|
||||||
// MemCachedStore around the current DAO Store.
|
// MemCachedStore around the current DAO Store.
|
||||||
func (dao *Simple) GetPrivate() *Simple {
|
func (dao *Simple) GetPrivate() *Simple {
|
||||||
d := &Simple{}
|
d := &Simple{
|
||||||
*d = *dao // Inherit everything...
|
Version: dao.Version,
|
||||||
|
keyBuf: dao.keyBuf,
|
||||||
|
dataBuf: dao.dataBuf,
|
||||||
|
} // Inherit everything...
|
||||||
d.Store = storage.NewPrivateMemCachedStore(dao.Store) // except storage, wrap another layer.
|
d.Store = storage.NewPrivateMemCachedStore(dao.Store) // except storage, wrap another layer.
|
||||||
d.private = true
|
d.private = true
|
||||||
|
d.nativeCachePS = dao
|
||||||
|
// Do not inherit cache from nativeCachePS; instead should create clear map:
|
||||||
|
// GetRWCache and GetROCache will retrieve cache from the underlying
|
||||||
|
// nativeCache if requested. The lowest underlying DAO MUST have its native
|
||||||
|
// cache initialized before access it, otherwise GetROCache and GetRWCache
|
||||||
|
// won't work properly.
|
||||||
|
d.nativeCache = make(map[int32]NativeContractCache)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -809,6 +839,17 @@ func (dao *Simple) getDataBuf() *io.BufBinWriter {
|
||||||
// Persist flushes all the changes made into the (supposedly) persistent
|
// Persist flushes all the changes made into the (supposedly) persistent
|
||||||
// underlying store. It doesn't block accesses to DAO from other threads.
|
// underlying store. It doesn't block accesses to DAO from other threads.
|
||||||
func (dao *Simple) Persist() (int, error) {
|
func (dao *Simple) Persist() (int, error) {
|
||||||
|
if dao.nativeCachePS != nil {
|
||||||
|
if !dao.private {
|
||||||
|
dao.nativeCacheLock.Lock()
|
||||||
|
defer dao.nativeCacheLock.Unlock()
|
||||||
|
}
|
||||||
|
if !dao.nativeCachePS.private {
|
||||||
|
dao.nativeCachePS.nativeCacheLock.Lock()
|
||||||
|
defer dao.nativeCachePS.nativeCacheLock.Unlock()
|
||||||
|
}
|
||||||
|
dao.persistNativeCache()
|
||||||
|
}
|
||||||
return dao.Store.Persist()
|
return dao.Store.Persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,5 +857,77 @@ func (dao *Simple) Persist() (int, error) {
|
||||||
// underlying store. It's a synchronous version of Persist that doesn't allow
|
// underlying store. It's a synchronous version of Persist that doesn't allow
|
||||||
// other threads to work with DAO while flushing the Store.
|
// other threads to work with DAO while flushing the Store.
|
||||||
func (dao *Simple) PersistSync() (int, error) {
|
func (dao *Simple) PersistSync() (int, error) {
|
||||||
|
if dao.nativeCachePS != nil {
|
||||||
|
dao.nativeCacheLock.Lock()
|
||||||
|
dao.nativeCachePS.nativeCacheLock.Lock()
|
||||||
|
defer func() {
|
||||||
|
dao.nativeCachePS.nativeCacheLock.Unlock()
|
||||||
|
dao.nativeCacheLock.Unlock()
|
||||||
|
}()
|
||||||
|
dao.persistNativeCache()
|
||||||
|
}
|
||||||
return dao.Store.PersistSync()
|
return dao.Store.PersistSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// persistNativeCache is internal unprotected method for native cache persisting.
|
||||||
|
// It does NO checks for nativeCachePS is not nil.
|
||||||
|
func (dao *Simple) persistNativeCache() {
|
||||||
|
lower := dao.nativeCachePS
|
||||||
|
for id, nativeCache := range dao.nativeCache {
|
||||||
|
lower.nativeCache[id] = nativeCache
|
||||||
|
}
|
||||||
|
dao.nativeCache = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetROCache returns native contact cache. The cache CAN NOT be modified by
|
||||||
|
// the caller. It's the caller's duty to keep it unmodified.
|
||||||
|
func (dao *Simple) GetROCache(id int32) NativeContractCache {
|
||||||
|
if !dao.private {
|
||||||
|
dao.nativeCacheLock.RLock()
|
||||||
|
defer dao.nativeCacheLock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.getCache(id, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRWCache returns native contact cache. The cache CAN BE safely modified
|
||||||
|
// by the caller.
|
||||||
|
func (dao *Simple) GetRWCache(id int32) NativeContractCache {
|
||||||
|
if !dao.private {
|
||||||
|
dao.nativeCacheLock.Lock()
|
||||||
|
defer dao.nativeCacheLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.getCache(id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCache is an internal unlocked representation of GetROCache and GetRWCache.
|
||||||
|
func (dao *Simple) getCache(k int32, ro bool) NativeContractCache {
|
||||||
|
if itm, ok := dao.nativeCache[k]; ok {
|
||||||
|
// Don't need to create itm copy, because its value was already copied
|
||||||
|
// the first time it was retrieved from loser ps.
|
||||||
|
return itm
|
||||||
|
}
|
||||||
|
|
||||||
|
if dao.nativeCachePS != nil {
|
||||||
|
if ro {
|
||||||
|
return dao.nativeCachePS.GetROCache(k)
|
||||||
|
}
|
||||||
|
v := dao.nativeCachePS.GetRWCache(k)
|
||||||
|
if v != nil {
|
||||||
|
// Create a copy here in order not to modify the existing cache.
|
||||||
|
cp := v.Copy()
|
||||||
|
dao.nativeCache[k] = cp
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCache adds native contract cache to the cache map.
|
||||||
|
func (dao *Simple) SetCache(id int32, v NativeContractCache) {
|
||||||
|
dao.nativeCacheLock.Lock()
|
||||||
|
defer dao.nativeCacheLock.Unlock()
|
||||||
|
|
||||||
|
dao.nativeCache[id] = v
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
@ -339,3 +340,31 @@ func (ic *Context) Exec() error {
|
||||||
defer ic.Finalize()
|
defer ic.Finalize()
|
||||||
return ic.VM.Run()
|
return ic.VM.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockHeight returns current block height got from Context's block if it's set.
|
||||||
|
func (ic *Context) BlockHeight() uint32 {
|
||||||
|
if ic.Block != nil {
|
||||||
|
return ic.Block.Index - 1 // Persisting block is not yet stored.
|
||||||
|
}
|
||||||
|
return ic.Chain.BlockHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentBlockHash returns current block hash got from Context's block if it's set.
|
||||||
|
func (ic *Context) CurrentBlockHash() util.Uint256 {
|
||||||
|
if ic.Block != nil {
|
||||||
|
return ic.Chain.GetHeaderHash(int(ic.Block.Index - 1)) // Persisting block is not yet stored.
|
||||||
|
}
|
||||||
|
return ic.Chain.CurrentBlockHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock returns block if it exists and available at the current Context's height.
|
||||||
|
func (ic *Context) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
|
block, err := ic.Chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block.Index > ic.BlockHeight() {
|
||||||
|
return nil, storage.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type policyChecker interface {
|
type policyChecker interface {
|
||||||
IsBlockedInternal(*dao.Simple, util.Uint160) bool
|
IsBlocked(*dao.Simple, util.Uint160) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadToken calls method specified by token id.
|
// LoadToken calls method specified by token id.
|
||||||
|
@ -97,7 +97,7 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
|
||||||
for _, nc := range ic.Natives {
|
for _, nc := range ic.Natives {
|
||||||
if nc.Metadata().Name == nativenames.Policy {
|
if nc.Metadata().Name == nativenames.Policy {
|
||||||
var pch = nc.(policyChecker)
|
var pch = nc.(policyChecker)
|
||||||
if pch.IsBlockedInternal(ic.DAO, cs.Hash) {
|
if pch.IsBlocked(ic.DAO, cs.Hash) {
|
||||||
return fmt.Errorf("contract %s is blocked", cs.Hash.StringLE())
|
return fmt.Errorf("contract %s is blocked", cs.Hash.StringLE())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"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/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||||
|
@ -530,10 +529,10 @@ func TestStorageFind(t *testing.T) {
|
||||||
|
|
||||||
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
||||||
|
|
||||||
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
|
func createVM(t testing.TB) (*vm.VM, *interop.Context, *Blockchain) {
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
context := chain.newInteropContext(trigger.Application,
|
context := chain.newInteropContext(trigger.Application,
|
||||||
dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions), nil, nil)
|
chain.dao.GetWrapped(), nil, nil)
|
||||||
v := context.SpawnVM()
|
v := context.SpawnVM()
|
||||||
return v, context, chain
|
return v, context, chain
|
||||||
}
|
}
|
||||||
|
@ -552,10 +551,7 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
chain := newTestChain(t)
|
v, context, chain := createVM(t)
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
|
|
||||||
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, contractState, context, chain
|
return v, contractState, context, chain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"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/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -17,8 +15,7 @@ func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context) er
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
v.Estack().PushVal(value)
|
v.Estack().PushVal(value)
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
|
context := chain.newInteropContext(trigger.Application, chain.dao, nil, nil)
|
||||||
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
|
||||||
context.VM = v
|
context.VM = v
|
||||||
require.Error(t, f(context))
|
require.Error(t, f(context))
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,7 +205,7 @@ func (b *Billet) incrementRefAndStore(h util.Uint256, bs []byte) {
|
||||||
// returned from `process` function. It also replaces all HashNodes to their
|
// returned from `process` function. It also replaces all HashNodes to their
|
||||||
// "unhashed" counterparts until the stop condition is satisfied.
|
// "unhashed" counterparts until the stop condition is satisfied.
|
||||||
func (b *Billet) Traverse(process func(pathToNode []byte, node Node, nodeBytes []byte) bool, ignoreStorageErr bool) error {
|
func (b *Billet) Traverse(process func(pathToNode []byte, node Node, nodeBytes []byte) bool, ignoreStorageErr bool) error {
|
||||||
r, err := b.traverse(b.root, []byte{}, []byte{}, process, ignoreStorageErr)
|
r, err := b.traverse(b.root, []byte{}, []byte{}, process, ignoreStorageErr, false)
|
||||||
if err != nil && !errors.Is(err, errStop) {
|
if err != nil && !errors.Is(err, errStop) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ func (b *Billet) Traverse(process func(pathToNode []byte, node Node, nodeBytes [
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode []byte, node Node, nodeBytes []byte) bool, ignoreStorageErr bool) (Node, error) {
|
func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode []byte, node Node, nodeBytes []byte) bool, ignoreStorageErr bool, backwards bool) (Node, error) {
|
||||||
if _, ok := curr.(EmptyNode); ok {
|
if _, ok := curr.(EmptyNode); ok {
|
||||||
// We're not interested in EmptyNodes, and they do not affect the
|
// We're not interested in EmptyNodes, and they do not affect the
|
||||||
// traversal process, thus remain them untouched.
|
// traversal process, thus remain them untouched.
|
||||||
|
@ -227,7 +227,7 @@ func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return b.traverse(r, path, from, process, ignoreStorageErr)
|
return b.traverse(r, path, from, process, ignoreStorageErr, backwards)
|
||||||
}
|
}
|
||||||
if len(from) == 0 {
|
if len(from) == 0 {
|
||||||
bytes := slice.Copy(curr.Bytes())
|
bytes := slice.Copy(curr.Bytes())
|
||||||
|
@ -242,22 +242,36 @@ func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode
|
||||||
var (
|
var (
|
||||||
startIndex byte
|
startIndex byte
|
||||||
endIndex byte = childrenCount
|
endIndex byte = childrenCount
|
||||||
|
cmp = func(i int) bool {
|
||||||
|
return i < int(endIndex)
|
||||||
|
}
|
||||||
|
step = 1
|
||||||
)
|
)
|
||||||
|
if backwards {
|
||||||
|
startIndex, endIndex = lastChild, startIndex
|
||||||
|
cmp = func(i int) bool {
|
||||||
|
return i >= int(endIndex)
|
||||||
|
}
|
||||||
|
step = -1
|
||||||
|
}
|
||||||
if len(from) != 0 {
|
if len(from) != 0 {
|
||||||
endIndex = lastChild
|
endIndex = lastChild
|
||||||
|
if backwards {
|
||||||
|
endIndex = 0
|
||||||
|
}
|
||||||
startIndex, from = splitPath(from)
|
startIndex, from = splitPath(from)
|
||||||
}
|
}
|
||||||
for i := startIndex; i < endIndex; i++ {
|
for i := int(startIndex); cmp(i); i += step {
|
||||||
var newPath []byte
|
var newPath []byte
|
||||||
if i == lastChild {
|
if i == lastChild {
|
||||||
newPath = path
|
newPath = path
|
||||||
} else {
|
} else {
|
||||||
newPath = append(path, i)
|
newPath = append(path, byte(i))
|
||||||
}
|
}
|
||||||
if i != startIndex {
|
if byte(i) != startIndex {
|
||||||
from = []byte{}
|
from = []byte{}
|
||||||
}
|
}
|
||||||
r, err := b.traverse(n.Children[i], newPath, from, process, ignoreStorageErr)
|
r, err := b.traverse(n.Children[i], newPath, from, process, ignoreStorageErr, backwards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errStop) {
|
if !errors.Is(err, errStop) {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -276,7 +290,7 @@ func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode
|
||||||
} else {
|
} else {
|
||||||
return b.tryCollapseExtension(n), nil
|
return b.tryCollapseExtension(n), nil
|
||||||
}
|
}
|
||||||
r, err := b.traverse(n.next, append(path, n.key...), from, process, ignoreStorageErr)
|
r, err := b.traverse(n.next, append(path, n.key...), from, process, ignoreStorageErr, backwards)
|
||||||
if err != nil && !errors.Is(err, errStop) {
|
if err != nil && !errors.Is(err, errStop) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -625,7 +625,7 @@ func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
|
||||||
}
|
}
|
||||||
return count >= max
|
return count >= max
|
||||||
}
|
}
|
||||||
_, err = b.traverse(start, path, fromP, process, false)
|
_, err = b.traverse(start, path, fromP, process, false, false)
|
||||||
if err != nil && !errors.Is(err, errStop) {
|
if err != nil && !errors.Is(err, errStop) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
126
pkg/core/mpt/trie_store.go
Normal file
126
pkg/core/mpt/trie_store.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package mpt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrieStore is an MPT-based storage implementation for storing and retrieving
|
||||||
|
// historic blockchain data. TrieStore is supposed to be used within transaction
|
||||||
|
// script invocations only, thus only contract storage related operations are
|
||||||
|
// supported. All storage-related operations are being performed using historical
|
||||||
|
// storage data retrieved from MPT state. TrieStore is read-only and does not
|
||||||
|
// support put-related operations, thus, it should always be wrapped into
|
||||||
|
// MemCachedStore for proper puts handling. TrieStore never changes the provided
|
||||||
|
// backend store.
|
||||||
|
type TrieStore struct {
|
||||||
|
trie *Trie
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrForbiddenTrieStoreOperation is returned when operation is not supposed to
|
||||||
|
// be performed over MPT-based Store.
|
||||||
|
var ErrForbiddenTrieStoreOperation = errors.New("operation is not allowed to be performed over TrieStore")
|
||||||
|
|
||||||
|
// NewTrieStore returns a new ready to use MPT-backed storage.
|
||||||
|
func NewTrieStore(root util.Uint256, mode TrieMode, backed storage.Store) *TrieStore {
|
||||||
|
cache, ok := backed.(*storage.MemCachedStore)
|
||||||
|
if !ok {
|
||||||
|
cache = storage.NewMemCachedStore(backed)
|
||||||
|
}
|
||||||
|
tr := NewTrie(NewHashNode(root), mode, cache)
|
||||||
|
return &TrieStore{
|
||||||
|
trie: tr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Store interface.
|
||||||
|
func (m *TrieStore) Get(key []byte) ([]byte, error) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, fmt.Errorf("%w: Get is supported only for contract storage items", ErrForbiddenTrieStoreOperation)
|
||||||
|
}
|
||||||
|
switch storage.KeyPrefix(key[0]) {
|
||||||
|
case storage.STStorage, storage.STTempStorage:
|
||||||
|
res, err := m.trie.Get(key[1:])
|
||||||
|
if err != nil && errors.Is(err, ErrNotFound) {
|
||||||
|
// Mimic the real storage behaviour.
|
||||||
|
return nil, storage.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: Get is supported only for contract storage items", ErrForbiddenTrieStoreOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutChangeSet implements the Store interface.
|
||||||
|
func (m *TrieStore) PutChangeSet(puts map[string][]byte, stor map[string][]byte) error {
|
||||||
|
// Only Get and Seek should be supported, as TrieStore is read-only and is always
|
||||||
|
// should be wrapped by MemCachedStore to properly support put operations (if any).
|
||||||
|
return fmt.Errorf("%w: PutChangeSet is not supported", ErrForbiddenTrieStoreOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek implements the Store interface.
|
||||||
|
func (m *TrieStore) Seek(rng storage.SeekRange, f func(k, v []byte) bool) {
|
||||||
|
prefix := storage.KeyPrefix(rng.Prefix[0])
|
||||||
|
if prefix != storage.STStorage && prefix != storage.STTempStorage { // Prefix is always non-empty.
|
||||||
|
panic(fmt.Errorf("%w: Seek is supported only for contract storage items", ErrForbiddenTrieStoreOperation))
|
||||||
|
}
|
||||||
|
prefixP := toNibbles(rng.Prefix[1:])
|
||||||
|
fromP := []byte{}
|
||||||
|
if len(rng.Start) > 0 {
|
||||||
|
fromP = toNibbles(rng.Start)
|
||||||
|
}
|
||||||
|
_, start, path, err := m.trie.getWithPath(m.trie.root, prefixP, false)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to determine the start node => no matching items.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path = path[len(prefixP):]
|
||||||
|
|
||||||
|
if len(fromP) > 0 {
|
||||||
|
if len(path) <= len(fromP) && bytes.HasPrefix(fromP, path) {
|
||||||
|
fromP = fromP[len(path):]
|
||||||
|
} else if len(path) > len(fromP) && bytes.HasPrefix(path, fromP) {
|
||||||
|
fromP = []byte{}
|
||||||
|
} else {
|
||||||
|
cmp := bytes.Compare(path, fromP)
|
||||||
|
if cmp < 0 == rng.Backwards {
|
||||||
|
// No matching items.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fromP = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := NewBillet(m.trie.root.Hash(), m.trie.mode, 0, m.trie.Store)
|
||||||
|
process := func(pathToNode []byte, node Node, _ []byte) bool {
|
||||||
|
if leaf, ok := node.(*LeafNode); ok {
|
||||||
|
// (*Billet).traverse includes `from` path into the result if so. It's OK for Seek, so shouldn't be filtered out.
|
||||||
|
kv := storage.KeyValue{
|
||||||
|
Key: append(slice.Copy(rng.Prefix), pathToNode...), // Do not cut prefix.
|
||||||
|
Value: slice.Copy(leaf.value),
|
||||||
|
}
|
||||||
|
return !f(kv.Key, kv.Value) // Should return whether to stop.
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = b.traverse(start, path, fromP, process, false, rng.Backwards)
|
||||||
|
if err != nil && !errors.Is(err, errStop) {
|
||||||
|
panic(fmt.Errorf("failed to perform Seek operation on TrieStore: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeekGC implements the Store interface.
|
||||||
|
func (m *TrieStore) SeekGC(rng storage.SeekRange, keep func(k, v []byte) bool) error {
|
||||||
|
return fmt.Errorf("%w: SeekGC is not supported", ErrForbiddenTrieStoreOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Store interface.
|
||||||
|
func (m *TrieStore) Close() error {
|
||||||
|
m.trie = nil
|
||||||
|
return nil
|
||||||
|
}
|
73
pkg/core/mpt/trie_store_test.go
Normal file
73
pkg/core/mpt/trie_store_test.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package mpt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrieStore_TestTrieOperations(t *testing.T) {
|
||||||
|
source := newTestTrie(t)
|
||||||
|
backed := source.Store
|
||||||
|
|
||||||
|
st := NewTrieStore(source.root.Hash(), ModeAll, backed)
|
||||||
|
|
||||||
|
t.Run("forbidden operations", func(t *testing.T) {
|
||||||
|
require.ErrorIs(t, st.SeekGC(storage.SeekRange{}, nil), ErrForbiddenTrieStoreOperation)
|
||||||
|
_, err := st.Get([]byte{byte(storage.STTokenTransferInfo)})
|
||||||
|
require.ErrorIs(t, err, ErrForbiddenTrieStoreOperation)
|
||||||
|
require.ErrorIs(t, st.PutChangeSet(nil, nil), ErrForbiddenTrieStoreOperation)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get", func(t *testing.T) {
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
res, err := st.Get(append([]byte{byte(storage.STStorage)}, 0xAC, 0xae)) // leaf `hello`
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []byte("hello"), res)
|
||||||
|
})
|
||||||
|
t.Run("bad path", func(t *testing.T) {
|
||||||
|
_, err := st.Get(append([]byte{byte(storage.STStorage)}, 0xAC, 0xa0)) // bad path
|
||||||
|
require.ErrorIs(t, err, storage.ErrKeyNotFound)
|
||||||
|
})
|
||||||
|
t.Run("path to not-a-leaf", func(t *testing.T) {
|
||||||
|
_, err := st.Get(append([]byte{byte(storage.STStorage)}, 0xAC)) // path to extension
|
||||||
|
require.ErrorIs(t, err, storage.ErrKeyNotFound)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Seek", func(t *testing.T) {
|
||||||
|
check := func(t *testing.T, backwards bool) {
|
||||||
|
var res [][]byte
|
||||||
|
st.Seek(storage.SeekRange{
|
||||||
|
Prefix: []byte{byte(storage.STStorage)},
|
||||||
|
Start: nil,
|
||||||
|
Backwards: backwards,
|
||||||
|
}, func(k, v []byte) bool {
|
||||||
|
res = append(res, k)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
require.Equal(t, 4, len(res))
|
||||||
|
for i := 0; i < len(res); i++ {
|
||||||
|
require.Equal(t, byte(storage.STStorage), res[i][0])
|
||||||
|
if i < len(res)-1 {
|
||||||
|
cmp := bytes.Compare(res[i], res[i+1])
|
||||||
|
if backwards {
|
||||||
|
require.True(t, cmp > 0)
|
||||||
|
} else {
|
||||||
|
require.True(t, cmp < 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("good: over whole storage", func(t *testing.T) {
|
||||||
|
t.Run("forwards", func(t *testing.T) {
|
||||||
|
check(t, false)
|
||||||
|
})
|
||||||
|
t.Run("backwards", func(t *testing.T) {
|
||||||
|
check(t, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
|
||||||
cs.Contracts = append(cs.Contracts, ledger)
|
cs.Contracts = append(cs.Contracts, ledger)
|
||||||
|
|
||||||
gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions)
|
gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions)
|
||||||
neo := newNEO()
|
neo := newNEO(cfg)
|
||||||
policy := newPolicy()
|
policy := newPolicy()
|
||||||
neo.GAS = gas
|
neo.GAS = gas
|
||||||
neo.Policy = policy
|
neo.Policy = policy
|
||||||
|
|
|
@ -3,6 +3,7 @@ package native
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -31,12 +32,6 @@ type Designate struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
|
|
||||||
rolesChangedFlag atomic.Value
|
|
||||||
oracles atomic.Value
|
|
||||||
stateVals atomic.Value
|
|
||||||
neofsAlphabet atomic.Value
|
|
||||||
notaries atomic.Value
|
|
||||||
|
|
||||||
// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
|
// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
|
||||||
p2pSigExtensionsEnabled bool
|
p2pSigExtensionsEnabled bool
|
||||||
|
|
||||||
|
@ -53,6 +48,16 @@ type roleData struct {
|
||||||
height uint32
|
height uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DesignationCache struct {
|
||||||
|
// rolesChangedFlag shows whether any of designated nodes were changed within the current block.
|
||||||
|
// It is used to notify dependant services about updated node roles during PostPersist.
|
||||||
|
rolesChangedFlag bool
|
||||||
|
oracles roleData
|
||||||
|
stateVals roleData
|
||||||
|
neofsAlphabet roleData
|
||||||
|
notaries roleData
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
designateContractID = -8
|
designateContractID = -8
|
||||||
|
|
||||||
|
@ -73,6 +78,22 @@ var (
|
||||||
ErrNoBlock = errors.New("no persisting block in the context")
|
ErrNoBlock = errors.New("no persisting block in the context")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ interop.Contract = (*Designate)(nil)
|
||||||
|
_ dao.NativeContractCache = (*DesignationCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *DesignationCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &DesignationCache{}
|
||||||
|
copyDesignationCache(c, cp)
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDesignationCache(src, dst *DesignationCache) {
|
||||||
|
*dst = *src
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Designate) isValidRole(r noderoles.Role) bool {
|
func (s *Designate) isValidRole(r noderoles.Role) bool {
|
||||||
return r == noderoles.Oracle || r == noderoles.StateValidator ||
|
return r == noderoles.Oracle || r == noderoles.StateValidator ||
|
||||||
r == noderoles.NeoFSAlphabet || (s.p2pSigExtensionsEnabled && r == noderoles.P2PNotary)
|
r == noderoles.NeoFSAlphabet || (s.p2pSigExtensionsEnabled && r == noderoles.P2PNotary)
|
||||||
|
@ -102,8 +123,30 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes Oracle contract.
|
// Initialize initializes Designation contract. It is called once at native Management's OnPersist
|
||||||
|
// at the genesis block, and we can't properly fill the cache at this point, as there are no roles
|
||||||
|
// data in the storage.
|
||||||
func (s *Designate) Initialize(ic *interop.Context) error {
|
func (s *Designate) Initialize(ic *interop.Context) error {
|
||||||
|
cache := &DesignationCache{}
|
||||||
|
ic.DAO.SetCache(s.ID, cache)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeCache fills native Designate cache from DAO. It is called at non-zero height, thus
|
||||||
|
// we can fetch the roles data right from the storage.
|
||||||
|
func (s *Designate) InitializeCache(d *dao.Simple) error {
|
||||||
|
cache := &DesignationCache{}
|
||||||
|
roles := []noderoles.Role{noderoles.Oracle, noderoles.NeoFSAlphabet, noderoles.StateValidator}
|
||||||
|
if s.p2pSigExtensionsEnabled {
|
||||||
|
roles = append(roles, noderoles.P2PNotary)
|
||||||
|
}
|
||||||
|
for _, r := range roles {
|
||||||
|
err := s.updateCachedRoleData(cache, d, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get nodes from storage for %d role: %w", r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.SetCache(s.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,26 +157,19 @@ func (s *Designate) OnPersist(ic *interop.Context) error {
|
||||||
|
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (s *Designate) PostPersist(ic *interop.Context) error {
|
func (s *Designate) PostPersist(ic *interop.Context) error {
|
||||||
if !s.rolesChanged() {
|
cache := ic.DAO.GetRWCache(s.ID).(*DesignationCache)
|
||||||
|
if !cache.rolesChangedFlag {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.updateCachedRoleData(&s.oracles, ic.DAO, noderoles.Oracle); err != nil {
|
s.notifyRoleChanged(&cache.oracles, noderoles.Oracle)
|
||||||
return err
|
s.notifyRoleChanged(&cache.stateVals, noderoles.StateValidator)
|
||||||
}
|
s.notifyRoleChanged(&cache.neofsAlphabet, noderoles.NeoFSAlphabet)
|
||||||
if err := s.updateCachedRoleData(&s.stateVals, ic.DAO, noderoles.StateValidator); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.updateCachedRoleData(&s.neofsAlphabet, ic.DAO, noderoles.NeoFSAlphabet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.p2pSigExtensionsEnabled {
|
if s.p2pSigExtensionsEnabled {
|
||||||
if err := s.updateCachedRoleData(&s.notaries, ic.DAO, noderoles.P2PNotary); err != nil {
|
s.notifyRoleChanged(&cache.notaries, noderoles.P2PNotary)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.rolesChangedFlag.Store(false)
|
cache.rolesChangedFlag = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +188,7 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It
|
||||||
panic(ErrInvalidIndex)
|
panic(ErrInvalidIndex)
|
||||||
}
|
}
|
||||||
index := ind.Uint64()
|
index := ind.Uint64()
|
||||||
if index > uint64(ic.Chain.BlockHeight()+1) {
|
if index > uint64(ic.BlockHeight()+1) {
|
||||||
panic(ErrInvalidIndex)
|
panic(ErrInvalidIndex)
|
||||||
}
|
}
|
||||||
pubs, _, err := s.GetDesignatedByRole(ic.DAO, r, uint32(index))
|
pubs, _, err := s.GetDesignatedByRole(ic.DAO, r, uint32(index))
|
||||||
|
@ -162,11 +198,6 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It
|
||||||
return pubsToArray(pubs)
|
return pubsToArray(pubs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Designate) rolesChanged() bool {
|
|
||||||
rc := s.rolesChangedFlag.Load()
|
|
||||||
return rc == nil || rc.(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.Uint160 {
|
func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.Uint160 {
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
return util.Uint160{}
|
return util.Uint160{}
|
||||||
|
@ -181,47 +212,58 @@ func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.
|
||||||
return hash.Hash160(script)
|
return hash.Hash160(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Designate) updateCachedRoleData(v *atomic.Value, d *dao.Simple, r noderoles.Role) error {
|
// updateCachedRoleData fetches the most recent role data from the storage and
|
||||||
nodeKeys, height, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
// updates the given cache.
|
||||||
|
func (s *Designate) updateCachedRoleData(cache *DesignationCache, d *dao.Simple, r noderoles.Role) error {
|
||||||
|
var v *roleData
|
||||||
|
switch r {
|
||||||
|
case noderoles.Oracle:
|
||||||
|
v = &cache.oracles
|
||||||
|
case noderoles.StateValidator:
|
||||||
|
v = &cache.stateVals
|
||||||
|
case noderoles.NeoFSAlphabet:
|
||||||
|
v = &cache.neofsAlphabet
|
||||||
|
case noderoles.P2PNotary:
|
||||||
|
v = &cache.notaries
|
||||||
|
}
|
||||||
|
nodeKeys, height, err := s.getDesignatedByRoleFromStorage(d, r, math.MaxUint32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Store(&roleData{
|
v.nodes = nodeKeys
|
||||||
nodes: nodeKeys,
|
v.addr = s.hashFromNodes(r, nodeKeys)
|
||||||
addr: s.hashFromNodes(r, nodeKeys),
|
v.height = height
|
||||||
height: height,
|
cache.rolesChangedFlag = true
|
||||||
})
|
|
||||||
switch r {
|
|
||||||
case noderoles.Oracle:
|
|
||||||
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
|
||||||
orc.UpdateOracleNodes(nodeKeys.Copy())
|
|
||||||
}
|
|
||||||
case noderoles.P2PNotary:
|
|
||||||
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
|
||||||
ntr.UpdateNotaryNodes(nodeKeys.Copy())
|
|
||||||
}
|
|
||||||
case noderoles.StateValidator:
|
|
||||||
if s.StateRootService != nil {
|
|
||||||
s.StateRootService.UpdateStateValidators(height, nodeKeys.Copy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Designate) getCachedRoleData(r noderoles.Role) *roleData {
|
func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) {
|
||||||
var val interface{}
|
|
||||||
switch r {
|
switch r {
|
||||||
case noderoles.Oracle:
|
case noderoles.Oracle:
|
||||||
val = s.oracles.Load()
|
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
||||||
case noderoles.StateValidator:
|
orc.UpdateOracleNodes(v.nodes.Copy())
|
||||||
val = s.stateVals.Load()
|
}
|
||||||
case noderoles.NeoFSAlphabet:
|
|
||||||
val = s.neofsAlphabet.Load()
|
|
||||||
case noderoles.P2PNotary:
|
case noderoles.P2PNotary:
|
||||||
val = s.notaries.Load()
|
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
||||||
|
ntr.UpdateNotaryNodes(v.nodes.Copy())
|
||||||
|
}
|
||||||
|
case noderoles.StateValidator:
|
||||||
|
if s.StateRootService != nil {
|
||||||
|
s.StateRootService.UpdateStateValidators(v.height, v.nodes.Copy())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if val != nil {
|
}
|
||||||
return val.(*roleData)
|
|
||||||
|
func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData {
|
||||||
|
switch r {
|
||||||
|
case noderoles.Oracle:
|
||||||
|
return &cache.oracles
|
||||||
|
case noderoles.StateValidator:
|
||||||
|
return &cache.stateVals
|
||||||
|
case noderoles.NeoFSAlphabet:
|
||||||
|
return &cache.neofsAlphabet
|
||||||
|
case noderoles.P2PNotary:
|
||||||
|
return &cache.notaries
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -231,17 +273,11 @@ func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util
|
||||||
if !s.isValidRole(r) {
|
if !s.isValidRole(r) {
|
||||||
return util.Uint160{}, ErrInvalidRole
|
return util.Uint160{}, ErrInvalidRole
|
||||||
}
|
}
|
||||||
if !s.rolesChanged() {
|
cache := d.GetROCache(s.ID).(*DesignationCache)
|
||||||
if val := s.getCachedRoleData(r); val != nil {
|
if val := getCachedRoleData(cache, r); val != nil {
|
||||||
return val.addr, nil
|
return val.addr, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nodes, _, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
return util.Uint160{}, nil
|
||||||
if err != nil {
|
|
||||||
return util.Uint160{}, err
|
|
||||||
}
|
|
||||||
// We only have hashing defined for oracles now.
|
|
||||||
return s.hashFromNodes(r, nodes), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDesignatedByRole returns nodes for role r.
|
// GetDesignatedByRole returns nodes for role r.
|
||||||
|
@ -249,11 +285,22 @@ func (s *Designate) GetDesignatedByRole(d *dao.Simple, r noderoles.Role, index u
|
||||||
if !s.isValidRole(r) {
|
if !s.isValidRole(r) {
|
||||||
return nil, 0, ErrInvalidRole
|
return nil, 0, ErrInvalidRole
|
||||||
}
|
}
|
||||||
if !s.rolesChanged() {
|
cache := d.GetROCache(s.ID).(*DesignationCache)
|
||||||
if val := s.getCachedRoleData(r); val != nil && val.height <= index {
|
if val := getCachedRoleData(cache, r); val != nil {
|
||||||
|
if val.height <= index {
|
||||||
return val.nodes.Copy(), val.height, nil
|
return val.nodes.Copy(), val.height, nil
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Cache is always valid, thus if there's no cache then there's no designated nodes for this role.
|
||||||
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
|
// Cache stores only latest designated nodes, so if the old info is requested, then we still need
|
||||||
|
// to search in the storage.
|
||||||
|
return s.getDesignatedByRoleFromStorage(d, r, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDesignatedByRoleFromStorage returns nodes for role r from the storage.
|
||||||
|
func (s *Designate) getDesignatedByRoleFromStorage(d *dao.Simple, r noderoles.Role, index uint32) (keys.PublicKeys, uint32, error) {
|
||||||
var (
|
var (
|
||||||
ns NodeList
|
ns NodeList
|
||||||
bestIndex uint32
|
bestIndex uint32
|
||||||
|
@ -310,7 +357,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
||||||
if !s.isValidRole(r) {
|
if !s.isValidRole(r) {
|
||||||
return ErrInvalidRole
|
return ErrInvalidRole
|
||||||
}
|
}
|
||||||
h := s.NEO.GetCommitteeAddress()
|
h := s.NEO.GetCommitteeAddress(ic.DAO)
|
||||||
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
|
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
|
||||||
return ErrInvalidWitness
|
return ErrInvalidWitness
|
||||||
}
|
}
|
||||||
|
@ -327,12 +374,18 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
||||||
}
|
}
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
nl := NodeList(pubs)
|
nl := NodeList(pubs)
|
||||||
s.rolesChangedFlag.Store(true)
|
|
||||||
err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
|
err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache := ic.DAO.GetRWCache(s.ID).(*DesignationCache)
|
||||||
|
err = s.updateCachedRoleData(cache, ic.DAO, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update Designation role data cache: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
|
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
|
||||||
ScriptHash: s.Hash,
|
ScriptHash: s.Hash,
|
||||||
Name: DesignationEventName,
|
Name: DesignationEventName,
|
||||||
|
@ -355,8 +408,3 @@ func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {
|
||||||
u := bi.Uint64()
|
u := bi.Uint64()
|
||||||
return noderoles.Role(u), u <= math.MaxUint8 && s.isValidRole(noderoles.Role(u))
|
return noderoles.Role(u), u <= math.MaxUint8 && s.isValidRole(noderoles.Role(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeCache invalidates native Designate cache.
|
|
||||||
func (s *Designate) InitializeCache() {
|
|
||||||
s.rolesChangedFlag.Store(true)
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ func Call(ic *interop.Context) error {
|
||||||
if len(history) == 0 {
|
if len(history) == 0 {
|
||||||
return fmt.Errorf("native contract %s is disabled", c.Metadata().Name)
|
return fmt.Errorf("native contract %s is disabled", c.Metadata().Name)
|
||||||
}
|
}
|
||||||
if history[0] > ic.Chain.BlockHeight() {
|
if history[0] > ic.BlockHeight() {
|
||||||
return fmt.Errorf("native contract %s is active after height = %d", c.Metadata().Name, history[0])
|
return fmt.Errorf("native contract %s is active after height = %d", c.Metadata().Name, history[0])
|
||||||
}
|
}
|
||||||
m, ok := c.Metadata().GetMethodByOffset(ic.VM.Context().IP())
|
m, ok := c.Metadata().GetMethodByOffset(ic.VM.Context().IP())
|
||||||
|
|
|
@ -103,19 +103,19 @@ func (l *Ledger) PostPersist(ic *interop.Context) error {
|
||||||
|
|
||||||
// currentHash implements currentHash SC method.
|
// currentHash implements currentHash SC method.
|
||||||
func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
return stackitem.Make(ic.Chain.CurrentBlockHash().BytesBE())
|
return stackitem.Make(ic.CurrentBlockHash().BytesBE())
|
||||||
}
|
}
|
||||||
|
|
||||||
// currentIndex implements currentIndex SC method.
|
// currentIndex implements currentIndex SC method.
|
||||||
func (l *Ledger) currentIndex(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (l *Ledger) currentIndex(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
return stackitem.Make(ic.Chain.BlockHeight())
|
return stackitem.Make(ic.BlockHeight())
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlock implements getBlock SC method.
|
// getBlock implements getBlock SC method.
|
||||||
func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||||
hash := getBlockHashFromItem(ic.Chain, params[0])
|
hash := getBlockHashFromItem(ic, params[0])
|
||||||
block, err := ic.Chain.GetBlock(hash)
|
block, err := ic.GetBlock(hash)
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, block.Index) {
|
if err != nil || !isTraceableBlock(ic, block.Index) {
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
return BlockToStackItem(block)
|
return BlockToStackItem(block)
|
||||||
|
@ -124,7 +124,7 @@ func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackite
|
||||||
// getTransaction returns transaction to the SC.
|
// getTransaction returns transaction to the SC.
|
||||||
func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||||
tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
if err != nil || !isTraceableBlock(ic, h) {
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
return TransactionToStackItem(tx)
|
return TransactionToStackItem(tx)
|
||||||
|
@ -133,7 +133,7 @@ func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) st
|
||||||
// getTransactionHeight returns transaction height to the SC.
|
// getTransactionHeight returns transaction height to the SC.
|
||||||
func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||||
_, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
_, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
if err != nil || !isTraceableBlock(ic, h) {
|
||||||
return stackitem.Make(-1)
|
return stackitem.Make(-1)
|
||||||
}
|
}
|
||||||
return stackitem.Make(h)
|
return stackitem.Make(h)
|
||||||
|
@ -142,10 +142,10 @@ func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.It
|
||||||
// getTransactionFromBlock returns transaction with the given index from the
|
// getTransactionFromBlock returns transaction with the given index from the
|
||||||
// block with height or hash specified.
|
// block with height or hash specified.
|
||||||
func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||||
hash := getBlockHashFromItem(ic.Chain, params[0])
|
hash := getBlockHashFromItem(ic, params[0])
|
||||||
index := toUint32(params[1])
|
index := toUint32(params[1])
|
||||||
block, err := ic.Chain.GetBlock(hash)
|
block, err := ic.GetBlock(hash)
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, block.Index) {
|
if err != nil || !isTraceableBlock(ic, block.Index) {
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
if index >= uint32(len(block.Transactions)) {
|
if index >= uint32(len(block.Transactions)) {
|
||||||
|
@ -157,7 +157,7 @@ func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem
|
||||||
// getTransactionSigners returns transaction signers to the SC.
|
// getTransactionSigners returns transaction signers to the SC.
|
||||||
func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||||
tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
if err != nil || !isTraceableBlock(ic, h) {
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
return SignersToStackItem(tx.Signers)
|
return SignersToStackItem(tx.Signers)
|
||||||
|
@ -170,7 +170,7 @@ func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.I
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
h, _, aer, err := ic.DAO.GetTxExecResult(hash)
|
h, _, aer, err := ic.DAO.GetTxExecResult(hash)
|
||||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
if err != nil || !isTraceableBlock(ic, h) {
|
||||||
return stackitem.Make(vm.NoneState)
|
return stackitem.Make(vm.NoneState)
|
||||||
}
|
}
|
||||||
return stackitem.Make(aer.VMState)
|
return stackitem.Make(aer.VMState)
|
||||||
|
@ -178,9 +178,9 @@ func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.I
|
||||||
|
|
||||||
// isTraceableBlock defines whether we're able to give information about
|
// isTraceableBlock defines whether we're able to give information about
|
||||||
// the block with index specified.
|
// the block with index specified.
|
||||||
func isTraceableBlock(bc interop.Ledger, index uint32) bool {
|
func isTraceableBlock(ic *interop.Context, index uint32) bool {
|
||||||
height := bc.BlockHeight()
|
height := ic.BlockHeight()
|
||||||
MaxTraceableBlocks := bc.GetConfig().MaxTraceableBlocks
|
MaxTraceableBlocks := ic.Chain.GetConfig().MaxTraceableBlocks
|
||||||
return index <= height && index+MaxTraceableBlocks > height
|
return index <= height && index+MaxTraceableBlocks > height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,17 +188,17 @@ func isTraceableBlock(bc interop.Ledger, index uint32) bool {
|
||||||
// Ledger if needed. Interop functions accept both block numbers and
|
// Ledger if needed. Interop functions accept both block numbers and
|
||||||
// block hashes as parameters, thus this function is needed. It's supposed to
|
// 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.
|
// be called within VM context, so it panics if anything goes wrong.
|
||||||
func getBlockHashFromItem(bc interop.Ledger, item stackitem.Item) util.Uint256 {
|
func getBlockHashFromItem(ic *interop.Context, item stackitem.Item) util.Uint256 {
|
||||||
bigindex, err := item.TryInteger()
|
bigindex, err := item.TryInteger()
|
||||||
if err == nil && bigindex.IsUint64() {
|
if err == nil && bigindex.IsUint64() {
|
||||||
index := bigindex.Uint64()
|
index := bigindex.Uint64()
|
||||||
if index > math.MaxUint32 {
|
if index > math.MaxUint32 {
|
||||||
panic("bad block index")
|
panic("bad block index")
|
||||||
}
|
}
|
||||||
if uint32(index) > bc.BlockHeight() {
|
if uint32(index) > ic.BlockHeight() {
|
||||||
panic(fmt.Errorf("no block with index %d", index))
|
panic(fmt.Errorf("no block with index %d", index))
|
||||||
}
|
}
|
||||||
return bc.GetHeaderHash(int(index))
|
return ic.Chain.GetHeaderHash(int(index))
|
||||||
}
|
}
|
||||||
hash, err := getUint256FromItem(item)
|
hash, err := getUint256FromItem(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
@ -30,8 +29,9 @@ import (
|
||||||
type Management struct {
|
type Management struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
|
}
|
||||||
|
|
||||||
mtx sync.RWMutex
|
type ManagementCache struct {
|
||||||
contracts map[util.Uint160]*state.Contract
|
contracts map[util.Uint160]*state.Contract
|
||||||
// nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist.
|
// nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist.
|
||||||
nep11 map[util.Uint160]struct{}
|
nep11 map[util.Uint160]struct{}
|
||||||
|
@ -57,6 +57,33 @@ var (
|
||||||
keyMinimumDeploymentFee = []byte{20}
|
keyMinimumDeploymentFee = []byte{20}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ interop.Contract = (*Management)(nil)
|
||||||
|
_ dao.NativeContractCache = (*ManagementCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *ManagementCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &ManagementCache{
|
||||||
|
contracts: make(map[util.Uint160]*state.Contract),
|
||||||
|
nep11: make(map[util.Uint160]struct{}),
|
||||||
|
nep17: make(map[util.Uint160]struct{}),
|
||||||
|
}
|
||||||
|
// Copy the whole set of contracts is too expensive. We will create a separate map
|
||||||
|
// holding the same set of pointers to contracts, and in case if some contract is
|
||||||
|
// supposed to be changed, Management will create the copy in-place.
|
||||||
|
for hash, ctr := range c.contracts {
|
||||||
|
cp.contracts[hash] = ctr
|
||||||
|
}
|
||||||
|
for hash := range c.nep17 {
|
||||||
|
cp.nep17[hash] = struct{}{}
|
||||||
|
}
|
||||||
|
for hash := range c.nep11 {
|
||||||
|
cp.nep11[hash] = struct{}{}
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
// MakeContractKey creates a key from account script hash.
|
// MakeContractKey creates a key from account script hash.
|
||||||
func MakeContractKey(h util.Uint160) []byte {
|
func MakeContractKey(h util.Uint160) []byte {
|
||||||
return makeUint160Key(prefixContract, h)
|
return makeUint160Key(prefixContract, h)
|
||||||
|
@ -66,9 +93,6 @@ func MakeContractKey(h util.Uint160) []byte {
|
||||||
func newManagement() *Management {
|
func newManagement() *Management {
|
||||||
var m = &Management{
|
var m = &Management{
|
||||||
ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID),
|
ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID),
|
||||||
contracts: make(map[util.Uint160]*state.Contract),
|
|
||||||
nep11: make(map[util.Uint160]struct{}),
|
|
||||||
nep17: make(map[util.Uint160]struct{}),
|
|
||||||
}
|
}
|
||||||
defer m.UpdateHash()
|
defer m.UpdateHash()
|
||||||
|
|
||||||
|
@ -146,25 +170,12 @@ func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) sta
|
||||||
|
|
||||||
// GetContract returns contract with given hash from given DAO.
|
// GetContract returns contract with given hash from given DAO.
|
||||||
func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
|
func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
|
||||||
m.mtx.RLock()
|
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||||
cs, ok := m.contracts[hash]
|
cs, ok := cache.contracts[hash]
|
||||||
m.mtx.RUnlock()
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, storage.ErrKeyNotFound
|
return nil, storage.ErrKeyNotFound
|
||||||
} else if cs != nil {
|
|
||||||
return cs, nil
|
|
||||||
}
|
}
|
||||||
return m.getContractFromDAO(d, hash)
|
return cs, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Management) getContractFromDAO(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
|
|
||||||
contract := new(state.Contract)
|
|
||||||
key := MakeContractKey(hash)
|
|
||||||
err := getConvertibleFromDAO(m.ID, d, key, contract)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return contract, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
|
func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
|
||||||
|
@ -260,20 +271,23 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item)
|
||||||
return contractToStack(newcontract)
|
return contractToStack(newcontract)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Management) markUpdated(h util.Uint160) {
|
func (m *Management) markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Contract) {
|
||||||
m.mtx.Lock()
|
cache := d.GetRWCache(m.ID).(*ManagementCache)
|
||||||
// Just set it to nil, to refresh cache in `PostPersist`.
|
delete(cache.nep11, hash)
|
||||||
m.contracts[h] = nil
|
delete(cache.nep17, hash)
|
||||||
m.mtx.Unlock()
|
if cs == nil {
|
||||||
|
delete(cache.contracts, hash)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateContractCache(cache, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy creates contract's hash/ID and saves new contract into the given DAO.
|
// Deploy creates contract's hash/ID and saves new contract into the given DAO.
|
||||||
// It doesn't run _deploy method and doesn't emit notification.
|
// It doesn't run _deploy method and doesn't emit notification.
|
||||||
func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
|
func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
|
||||||
h := state.CreateContractHash(sender, neff.Checksum, manif.Name)
|
h := state.CreateContractHash(sender, neff.Checksum, manif.Name)
|
||||||
key := MakeContractKey(h)
|
_, err := m.GetContract(d, h)
|
||||||
si := d.GetStorageItem(m.ID, key)
|
if err == nil {
|
||||||
if si != nil {
|
|
||||||
return nil, errors.New("contract already exists")
|
return nil, errors.New("contract already exists")
|
||||||
}
|
}
|
||||||
id, err := m.getNextContractID(d)
|
id, err := m.getNextContractID(d)
|
||||||
|
@ -300,7 +314,6 @@ func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.markUpdated(newcontract.Hash)
|
|
||||||
return newcontract, nil
|
return newcontract, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +353,6 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma
|
||||||
contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract.
|
contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract.
|
||||||
// if NEF was provided, update the contract script
|
// if NEF was provided, update the contract script
|
||||||
if neff != nil {
|
if neff != nil {
|
||||||
m.markUpdated(hash)
|
|
||||||
contract.NEF = *neff
|
contract.NEF = *neff
|
||||||
}
|
}
|
||||||
// if manifest was provided, update the contract manifest
|
// if manifest was provided, update the contract manifest
|
||||||
|
@ -352,7 +364,6 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid manifest: %w", err)
|
return nil, fmt.Errorf("invalid manifest: %w", err)
|
||||||
}
|
}
|
||||||
m.markUpdated(hash)
|
|
||||||
contract.Manifest = *manif
|
contract.Manifest = *manif
|
||||||
}
|
}
|
||||||
err = checkScriptAndMethods(contract.NEF.Script, contract.Manifest.ABI.Methods)
|
err = checkScriptAndMethods(contract.NEF.Script, contract.Manifest.ABI.Methods)
|
||||||
|
@ -393,7 +404,7 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error {
|
||||||
d.DeleteStorageItem(contract.ID, k)
|
d.DeleteStorageItem(contract.ID, k)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
m.markUpdated(hash)
|
m.markUpdated(d, hash, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,18 +455,19 @@ func (m *Management) Metadata() *interop.ContractMD {
|
||||||
|
|
||||||
// updateContractCache saves contract in the common and NEP-related caches. It's
|
// updateContractCache saves contract in the common and NEP-related caches. It's
|
||||||
// an internal method that must be called with m.mtx lock taken.
|
// an internal method that must be called with m.mtx lock taken.
|
||||||
func (m *Management) updateContractCache(cs *state.Contract) {
|
func updateContractCache(cache *ManagementCache, cs *state.Contract) {
|
||||||
m.contracts[cs.Hash] = cs
|
cache.contracts[cs.Hash] = cs
|
||||||
if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) {
|
if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) {
|
||||||
m.nep11[cs.Hash] = struct{}{}
|
cache.nep11[cs.Hash] = struct{}{}
|
||||||
}
|
}
|
||||||
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
||||||
m.nep17[cs.Hash] = struct{}{}
|
cache.nep17[cs.Hash] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPersist implements Contract interface.
|
// OnPersist implements Contract interface.
|
||||||
func (m *Management) OnPersist(ic *interop.Context) error {
|
func (m *Management) OnPersist(ic *interop.Context) error {
|
||||||
|
var cache *ManagementCache
|
||||||
for _, native := range ic.Natives {
|
for _, native := range ic.Natives {
|
||||||
md := native.Metadata()
|
md := native.Metadata()
|
||||||
history := md.UpdateHistory
|
history := md.UpdateHistory
|
||||||
|
@ -466,16 +478,17 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
ContractBase: md.ContractBase,
|
ContractBase: md.ContractBase,
|
||||||
}
|
}
|
||||||
err := m.PutContractState(ic.DAO, cs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := native.Initialize(ic); err != nil {
|
if err := native.Initialize(ic); err != nil {
|
||||||
return fmt.Errorf("initializing %s native contract: %w", md.Name, err)
|
return fmt.Errorf("initializing %s native contract: %w", md.Name, err)
|
||||||
}
|
}
|
||||||
m.mtx.Lock()
|
err := m.putContractState(ic.DAO, cs, false) // Perform cache update manually.
|
||||||
m.updateContractCache(cs)
|
if err != nil {
|
||||||
m.mtx.Unlock()
|
return err
|
||||||
|
}
|
||||||
|
if cache == nil {
|
||||||
|
cache = ic.DAO.GetRWCache(m.ID).(*ManagementCache)
|
||||||
|
}
|
||||||
|
updateContractCache(cache, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -485,8 +498,11 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
||||||
// Cache initialisation should be done apart from Initialize because Initialize is
|
// Cache initialisation should be done apart from Initialize because Initialize is
|
||||||
// called only when deploying native contracts.
|
// called only when deploying native contracts.
|
||||||
func (m *Management) InitializeCache(d *dao.Simple) error {
|
func (m *Management) InitializeCache(d *dao.Simple) error {
|
||||||
m.mtx.Lock()
|
cache := &ManagementCache{
|
||||||
defer m.mtx.Unlock()
|
contracts: make(map[util.Uint160]*state.Contract),
|
||||||
|
nep11: make(map[util.Uint160]struct{}),
|
||||||
|
nep17: make(map[util.Uint160]struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
var initErr error
|
var initErr error
|
||||||
d.Seek(m.ID, storage.SeekRange{Prefix: []byte{prefixContract}}, func(_, v []byte) bool {
|
d.Seek(m.ID, storage.SeekRange{Prefix: []byte{prefixContract}}, func(_, v []byte) bool {
|
||||||
|
@ -495,56 +511,42 @@ func (m *Management) InitializeCache(d *dao.Simple) error {
|
||||||
if initErr != nil {
|
if initErr != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
m.updateContractCache(cs)
|
updateContractCache(cache, cs)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return initErr
|
if initErr != nil {
|
||||||
|
return initErr
|
||||||
|
}
|
||||||
|
d.SetCache(m.ID, cache)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (m *Management) PostPersist(ic *interop.Context) error {
|
func (m *Management) PostPersist(ic *interop.Context) error {
|
||||||
m.mtx.Lock()
|
|
||||||
for h, cs := range m.contracts {
|
|
||||||
if cs != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(m.nep11, h)
|
|
||||||
delete(m.nep17, h)
|
|
||||||
newCs, err := m.getContractFromDAO(ic.DAO, h)
|
|
||||||
if err != nil {
|
|
||||||
// Contract was destroyed.
|
|
||||||
delete(m.contracts, h)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m.updateContractCache(newCs)
|
|
||||||
}
|
|
||||||
m.mtx.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list
|
// GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list
|
||||||
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
|
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
|
||||||
// is returned.
|
// is returned.
|
||||||
func (m *Management) GetNEP11Contracts() []util.Uint160 {
|
func (m *Management) GetNEP11Contracts(d *dao.Simple) []util.Uint160 {
|
||||||
m.mtx.RLock()
|
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||||
result := make([]util.Uint160, 0, len(m.nep11))
|
result := make([]util.Uint160, 0, len(cache.nep11))
|
||||||
for h := range m.nep11 {
|
for h := range cache.nep11 {
|
||||||
result = append(result, h)
|
result = append(result, h)
|
||||||
}
|
}
|
||||||
m.mtx.RUnlock()
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list
|
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list
|
||||||
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
|
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
|
||||||
// is returned.
|
// is returned.
|
||||||
func (m *Management) GetNEP17Contracts() []util.Uint160 {
|
func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 {
|
||||||
m.mtx.RLock()
|
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||||
result := make([]util.Uint160, 0, len(m.nep17))
|
result := make([]util.Uint160, 0, len(cache.nep17))
|
||||||
for h := range m.nep17 {
|
for h := range cache.nep17 {
|
||||||
result = append(result, h)
|
result = append(result, h)
|
||||||
}
|
}
|
||||||
m.mtx.RUnlock()
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,16 +554,30 @@ func (m *Management) GetNEP17Contracts() []util.Uint160 {
|
||||||
func (m *Management) Initialize(ic *interop.Context) error {
|
func (m *Management) Initialize(ic *interop.Context) error {
|
||||||
setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee)
|
setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee)
|
||||||
setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1)
|
setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1)
|
||||||
|
|
||||||
|
cache := &ManagementCache{
|
||||||
|
contracts: make(map[util.Uint160]*state.Contract),
|
||||||
|
nep11: make(map[util.Uint160]struct{}),
|
||||||
|
nep17: make(map[util.Uint160]struct{}),
|
||||||
|
}
|
||||||
|
ic.DAO.SetCache(m.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutContractState saves given contract state into given DAO.
|
// PutContractState saves given contract state into given DAO.
|
||||||
func (m *Management) PutContractState(d *dao.Simple, cs *state.Contract) error {
|
func (m *Management) PutContractState(d *dao.Simple, cs *state.Contract) error {
|
||||||
|
return m.putContractState(d, cs, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putContractState is an internal PutContractState representation.
|
||||||
|
func (m *Management) putContractState(d *dao.Simple, cs *state.Contract, updateCache bool) error {
|
||||||
key := MakeContractKey(cs.Hash)
|
key := MakeContractKey(cs.Hash)
|
||||||
if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
|
if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.markUpdated(cs.Hash)
|
if updateCache {
|
||||||
|
m.markUpdated(d, cs.Hash, cs)
|
||||||
|
}
|
||||||
if cs.UpdateCounter != 0 { // Update.
|
if cs.UpdateCounter != 0 { // Update.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,8 +89,11 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), false, false)
|
d := dao.NewSimple(storage.NewMemoryStore(), false, false)
|
||||||
err := mgmt.Initialize(&interop.Context{DAO: d})
|
err := mgmt.Initialize(&interop.Context{DAO: d})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
err = mgmt.InitializeCache(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Empty(t, mgmt.GetNEP17Contracts())
|
require.Empty(t, mgmt.GetNEP17Contracts(d))
|
||||||
|
private := d.GetPrivate()
|
||||||
|
|
||||||
// Deploy NEP-17 contract
|
// Deploy NEP-17 contract
|
||||||
script := []byte{byte(opcode.RET)}
|
script := []byte{byte(opcode.RET)}
|
||||||
|
@ -104,29 +107,46 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
})
|
})
|
||||||
manif.SupportedStandards = []string{manifest.NEP17StandardName}
|
manif.SupportedStandards = []string{manifest.NEP17StandardName}
|
||||||
c1, err := mgmt.Deploy(d, sender, ne, manif)
|
c1, err := mgmt.Deploy(private, sender, ne, manif)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// PostPersist is not yet called, thus no NEP-17 contracts are expected
|
// c1 contract hash should be returned, as private DAO already contains changed cache.
|
||||||
require.Empty(t, mgmt.GetNEP17Contracts())
|
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(private))
|
||||||
|
|
||||||
// Call PostPersist, check c1 contract hash is returned
|
// Lower DAO still shouldn't contain c1, as no Persist was called.
|
||||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
require.Empty(t, mgmt.GetNEP17Contracts(d))
|
||||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
|
||||||
|
// Call Persist, check c1 contract hash is returned
|
||||||
|
_, err = private.Persist()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d))
|
||||||
|
|
||||||
// Update contract
|
// Update contract
|
||||||
|
private = d.GetPrivate()
|
||||||
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
|
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
|
||||||
Name: "dummy2",
|
Name: "dummy2",
|
||||||
ReturnType: smartcontract.VoidType,
|
ReturnType: smartcontract.VoidType,
|
||||||
Parameters: []manifest.Parameter{},
|
Parameters: []manifest.Parameter{},
|
||||||
})
|
})
|
||||||
c2, err := mgmt.Update(d, c1.Hash, ne, manif)
|
c1Updated, err := mgmt.Update(private, c1.Hash, ne, manif)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, c1.Hash, c1Updated.Hash)
|
||||||
|
|
||||||
// No changes expected before PostPersist call.
|
// No changes expected in lower store.
|
||||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d))
|
||||||
|
c1Lower, err := mgmt.GetContract(d, c1.Hash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(c1Lower.Manifest.ABI.Methods))
|
||||||
|
require.Equal(t, []util.Uint160{c1Updated.Hash}, mgmt.GetNEP17Contracts(private))
|
||||||
|
c1Upper, err := mgmt.GetContract(private, c1Updated.Hash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(c1Upper.Manifest.ABI.Methods))
|
||||||
|
|
||||||
// Call PostPersist, check c2 contract hash is returned
|
// Call Persist, check c1Updated state is returned from lower.
|
||||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
_, err = private.Persist()
|
||||||
require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts())
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d))
|
||||||
|
c1Lower, err = mgmt.GetContract(d, c1.Hash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(c1Lower.Manifest.ABI.Methods))
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
|
||||||
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
|
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
|
||||||
g.burn(ic, tx.Sender(), absAmount)
|
g.burn(ic, tx.Sender(), absAmount)
|
||||||
}
|
}
|
||||||
validators := g.NEO.GetNextBlockValidatorsInternal()
|
validators := g.NEO.GetNextBlockValidatorsInternal(ic.DAO)
|
||||||
primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
|
primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
|
||||||
var netFee int64
|
var netFee int64
|
||||||
for _, tx := range ic.Block.Transactions {
|
for _, tx := range ic.Block.Transactions {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
@ -35,33 +34,33 @@ type NEO struct {
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
Policy *Policy
|
Policy *Policy
|
||||||
|
|
||||||
// gasPerBlock represents current value of generated gas per block.
|
// Configuration and standby keys are set in constructor and then
|
||||||
// It is append-only and doesn't need to be copied when used.
|
// only read from.
|
||||||
gasPerBlock atomic.Value
|
cfg config.ProtocolConfiguration
|
||||||
gasPerBlockChanged atomic.Value
|
standbyKeys keys.PublicKeys
|
||||||
|
}
|
||||||
|
|
||||||
registerPrice atomic.Value
|
type NeoCache struct {
|
||||||
registerPriceChanged atomic.Value
|
// gasPerBlock represents the history of generated gas per block.
|
||||||
|
gasPerBlock gasRecord
|
||||||
|
|
||||||
votesChanged atomic.Value
|
registerPrice int64
|
||||||
nextValidators atomic.Value
|
|
||||||
validators atomic.Value
|
votesChanged bool
|
||||||
|
nextValidators keys.PublicKeys
|
||||||
|
validators keys.PublicKeys
|
||||||
// committee contains cached committee members and their votes.
|
// committee contains cached committee members and their votes.
|
||||||
// It is updated once in a while depending on committee size
|
// It is updated once in a while depending on committee size
|
||||||
// (every 28 blocks for mainnet). It's value
|
// (every 28 blocks for mainnet). It's value
|
||||||
// is always equal to value stored by `prefixCommittee`.
|
// is always equal to value stored by `prefixCommittee`.
|
||||||
committee atomic.Value
|
committee keysWithVotes
|
||||||
// committeeHash contains script hash of the committee.
|
// committeeHash contains script hash of the committee.
|
||||||
committeeHash atomic.Value
|
committeeHash util.Uint160
|
||||||
|
|
||||||
// gasPerVoteCache contains last updated value of GAS per vote reward for candidates.
|
// gasPerVoteCache contains last updated value of GAS per vote reward for candidates.
|
||||||
// It is set in state-modifying methods only and read in `PostPersist` thus is not protected
|
// It is set in state-modifying methods only and read in `PostPersist` thus is not protected
|
||||||
// by any mutex.
|
// by any mutex.
|
||||||
gasPerVoteCache map[string]big.Int
|
gasPerVoteCache map[string]big.Int
|
||||||
// Configuration and standby keys are set during initialization and then
|
|
||||||
// only read from.
|
|
||||||
cfg config.ProtocolConfiguration
|
|
||||||
standbyKeys keys.PublicKeys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -105,6 +104,41 @@ var (
|
||||||
big100 = big.NewInt(100)
|
big100 = big.NewInt(100)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ interop.Contract = (*NEO)(nil)
|
||||||
|
_ dao.NativeContractCache = (*NeoCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *NeoCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &NeoCache{}
|
||||||
|
copyNeoCache(c, cp)
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyNeoCache(src, dst *NeoCache) {
|
||||||
|
dst.votesChanged = src.votesChanged
|
||||||
|
// Can safely omit copying because the new array is created each time
|
||||||
|
// validators list, nextValidators and committee are updated.
|
||||||
|
dst.nextValidators = src.nextValidators
|
||||||
|
dst.validators = src.validators
|
||||||
|
dst.committee = src.committee
|
||||||
|
dst.committeeHash = src.committeeHash
|
||||||
|
|
||||||
|
dst.registerPrice = src.registerPrice
|
||||||
|
|
||||||
|
// Can't omit copying because gasPerBlock is append-only, thus to be able to
|
||||||
|
// discard cache changes in case of FAULTed transaction we need a separate
|
||||||
|
// container for updated gasPerBlock values.
|
||||||
|
dst.gasPerBlock = make(gasRecord, len(src.gasPerBlock))
|
||||||
|
copy(dst.gasPerBlock, src.gasPerBlock)
|
||||||
|
|
||||||
|
dst.gasPerVoteCache = make(map[string]big.Int)
|
||||||
|
for k, v := range src.gasPerVoteCache {
|
||||||
|
dst.gasPerVoteCache[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// makeValidatorKey creates a key from account script hash.
|
// makeValidatorKey creates a key from account script hash.
|
||||||
func makeValidatorKey(key *keys.PublicKey) []byte {
|
func makeValidatorKey(key *keys.PublicKey) []byte {
|
||||||
b := key.Bytes()
|
b := key.Bytes()
|
||||||
|
@ -116,7 +150,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNEO returns NEO native contract.
|
// newNEO returns NEO native contract.
|
||||||
func newNEO() *NEO {
|
func newNEO(cfg config.ProtocolConfiguration) *NEO {
|
||||||
n := &NEO{}
|
n := &NEO{}
|
||||||
defer n.UpdateHash()
|
defer n.UpdateHash()
|
||||||
|
|
||||||
|
@ -128,13 +162,11 @@ func newNEO() *NEO {
|
||||||
nep17.balFromBytes = n.balanceFromBytes
|
nep17.balFromBytes = n.balanceFromBytes
|
||||||
|
|
||||||
n.nep17TokenNative = *nep17
|
n.nep17TokenNative = *nep17
|
||||||
n.votesChanged.Store(true)
|
|
||||||
n.nextValidators.Store(keys.PublicKeys(nil))
|
err := n.initConfigCache(cfg)
|
||||||
n.validators.Store(keys.PublicKeys(nil))
|
if err != nil {
|
||||||
n.committee.Store(keysWithVotes(nil))
|
panic(fmt.Errorf("failed to initialize NEO config cache: %w", err))
|
||||||
n.committeeHash.Store(util.Uint160{})
|
}
|
||||||
n.registerPriceChanged.Store(true)
|
|
||||||
n.gasPerVoteCache = make(map[string]big.Int)
|
|
||||||
|
|
||||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||||
|
@ -198,10 +230,6 @@ func newNEO() *NEO {
|
||||||
|
|
||||||
// Initialize initializes NEO contract.
|
// Initialize initializes NEO contract.
|
||||||
func (n *NEO) Initialize(ic *interop.Context) error {
|
func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
err := n.initConfigCache(ic.Chain)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := n.nep17TokenNative.Initialize(ic); err != nil {
|
if err := n.nep17TokenNative.Initialize(ic); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -211,9 +239,17 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
return errors.New("already initialized")
|
return errors.New("already initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache := &NeoCache{
|
||||||
|
gasPerVoteCache: make(map[string]big.Int),
|
||||||
|
votesChanged: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need cache to be present in DAO before the subsequent call to `mint`.
|
||||||
|
ic.DAO.SetCache(n.ID, cache)
|
||||||
|
|
||||||
committee0 := n.standbyKeys[:n.cfg.GetCommitteeSize(ic.Block.Index)]
|
committee0 := n.standbyKeys[:n.cfg.GetCommitteeSize(ic.Block.Index)]
|
||||||
cvs := toKeysWithVotes(committee0)
|
cvs := toKeysWithVotes(committee0)
|
||||||
err = n.updateCache(cvs, ic.Chain)
|
err := n.updateCache(cache, cvs, ic.BlockHeight())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -231,80 +267,79 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
n.putGASRecord(ic.DAO, index, value)
|
n.putGASRecord(ic.DAO, index, value)
|
||||||
|
|
||||||
gr := &gasRecord{{Index: index, GASPerBlock: *value}}
|
gr := &gasRecord{{Index: index, GASPerBlock: *value}}
|
||||||
n.gasPerBlock.Store(*gr)
|
cache.gasPerBlock = *gr
|
||||||
n.gasPerBlockChanged.Store(false)
|
|
||||||
ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{})
|
ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{})
|
||||||
|
|
||||||
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
|
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
|
||||||
n.registerPrice.Store(int64(DefaultRegisterPrice))
|
cache.registerPrice = int64(DefaultRegisterPrice)
|
||||||
n.registerPriceChanged.Store(false)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeCache initializes all NEO cache with the proper values from storage.
|
// InitializeCache initializes all NEO cache with the proper values from storage.
|
||||||
// Cache initialisation should be done apart from Initialize because Initialize is
|
// Cache initialisation should be done apart from Initialize because Initialize is
|
||||||
// called only when deploying native contracts.
|
// called only when deploying native contracts.
|
||||||
func (n *NEO) InitializeCache(bc interop.Ledger, d *dao.Simple) error {
|
func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||||
err := n.initConfigCache(bc)
|
cache := &NeoCache{
|
||||||
if err != nil {
|
gasPerVoteCache: make(map[string]big.Int),
|
||||||
return nil
|
votesChanged: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var committee = keysWithVotes{}
|
var committee = keysWithVotes{}
|
||||||
si := d.GetStorageItem(n.ID, prefixCommittee)
|
si := d.GetStorageItem(n.ID, prefixCommittee)
|
||||||
if err := committee.DecodeBytes(si); err != nil {
|
if err := committee.DecodeBytes(si); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to decode committee: %w", err)
|
||||||
}
|
}
|
||||||
if err := n.updateCache(committee, bc); err != nil {
|
if err := n.updateCache(cache, committee, blockHeight); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to update cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d))
|
cache.gasPerBlock = n.getSortedGASRecordFromDAO(d)
|
||||||
n.gasPerBlockChanged.Store(false)
|
cache.registerPrice = getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
|
||||||
|
|
||||||
|
d.SetCache(n.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) initConfigCache(bc interop.Ledger) error {
|
func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
n.cfg = bc.GetConfig()
|
n.cfg = cfg
|
||||||
n.standbyKeys, err = keys.NewPublicKeysFromStrings(n.cfg.StandbyCommittee)
|
n.standbyKeys, err = keys.NewPublicKeysFromStrings(n.cfg.StandbyCommittee)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) updateCache(cvs keysWithVotes, bc interop.Ledger) error {
|
func (n *NEO) updateCache(cache *NeoCache, cvs keysWithVotes, blockHeight uint32) error {
|
||||||
n.committee.Store(cvs)
|
cache.committee = cvs
|
||||||
|
|
||||||
var committee = n.GetCommitteeMembers()
|
var committee = getCommitteeMembers(cache)
|
||||||
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.committeeHash.Store(hash.Hash160(script))
|
cache.committeeHash = hash.Hash160(script)
|
||||||
|
|
||||||
nextVals := committee[:n.cfg.GetNumOfCNs(bc.BlockHeight()+1)].Copy()
|
nextVals := committee[:n.cfg.GetNumOfCNs(blockHeight+1)].Copy()
|
||||||
sort.Sort(nextVals)
|
sort.Sort(nextVals)
|
||||||
n.nextValidators.Store(nextVals)
|
cache.nextValidators = nextVals
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) updateCommittee(ic *interop.Context) error {
|
func (n *NEO) updateCommittee(cache *NeoCache, ic *interop.Context) error {
|
||||||
votesChanged := n.votesChanged.Load().(bool)
|
if !cache.votesChanged {
|
||||||
if !votesChanged {
|
|
||||||
// We need to put in storage anyway, as it affects dumps
|
// We need to put in storage anyway, as it affects dumps
|
||||||
committee := n.committee.Load().(keysWithVotes)
|
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes())
|
||||||
ic.DAO.PutStorageItem(n.ID, prefixCommittee, committee.Bytes())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cvs, err := n.computeCommitteeMembers(ic.Chain, ic.DAO)
|
_, cvs, err := n.computeCommitteeMembers(ic.BlockHeight(), ic.DAO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := n.updateCache(cvs, ic.Chain); err != nil {
|
if err := n.updateCache(cache, cvs, ic.BlockHeight()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.votesChanged.Store(false)
|
cache.votesChanged = false
|
||||||
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes())
|
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -312,13 +347,14 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
|
||||||
// OnPersist implements Contract interface.
|
// OnPersist implements Contract interface.
|
||||||
func (n *NEO) OnPersist(ic *interop.Context) error {
|
func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||||
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
|
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
|
||||||
oldKeys := n.nextValidators.Load().(keys.PublicKeys)
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||||
oldCom := n.committee.Load().(keysWithVotes)
|
oldKeys := cache.nextValidators
|
||||||
|
oldCom := cache.committee
|
||||||
if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) ||
|
if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) ||
|
||||||
n.cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) {
|
n.cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) {
|
||||||
n.votesChanged.Store(true)
|
cache.votesChanged = true
|
||||||
}
|
}
|
||||||
if err := n.updateCommittee(ic); err != nil {
|
if err := n.updateCommittee(cache, ic); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +364,8 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
||||||
pubs := n.GetCommitteeMembers()
|
cache := ic.DAO.GetROCache(n.ID).(*NeoCache)
|
||||||
|
pubs := getCommitteeMembers(cache)
|
||||||
committeeSize := n.cfg.GetCommitteeSize(ic.Block.Index)
|
committeeSize := n.cfg.GetCommitteeSize(ic.Block.Index)
|
||||||
index := int(ic.Block.Index) % committeeSize
|
index := int(ic.Block.Index) % committeeSize
|
||||||
committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio)
|
committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio)
|
||||||
|
@ -342,8 +379,11 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
|
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
|
||||||
voterReward.Div(voterReward, big100)
|
voterReward.Div(voterReward, big100)
|
||||||
|
|
||||||
var cs = n.committee.Load().(keysWithVotes)
|
var (
|
||||||
var key = make([]byte, 38)
|
cs = cache.committee
|
||||||
|
isCacheRW bool
|
||||||
|
key = make([]byte, 38)
|
||||||
|
)
|
||||||
for i := range cs {
|
for i := range cs {
|
||||||
if cs[i].Votes.Sign() > 0 {
|
if cs[i].Votes.Sign() > 0 {
|
||||||
var tmp = new(big.Int)
|
var tmp = new(big.Int)
|
||||||
|
@ -358,7 +398,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
key = makeVoterKey([]byte(cs[i].Key), key)
|
key = makeVoterKey([]byte(cs[i].Key), key)
|
||||||
|
|
||||||
var r *big.Int
|
var r *big.Int
|
||||||
if g, ok := n.gasPerVoteCache[cs[i].Key]; ok {
|
if g, ok := cache.gasPerVoteCache[cs[i].Key]; ok {
|
||||||
r = &g
|
r = &g
|
||||||
} else {
|
} else {
|
||||||
reward := n.getGASPerVote(ic.DAO, key[:34], []uint32{ic.Block.Index + 1})
|
reward := n.getGASPerVote(ic.DAO, key[:34], []uint32{ic.Block.Index + 1})
|
||||||
|
@ -367,22 +407,16 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
tmp.Add(tmp, r)
|
tmp.Add(tmp, r)
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1)
|
binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1)
|
||||||
n.gasPerVoteCache[cs[i].Key] = *tmp
|
if !isCacheRW {
|
||||||
|
cache = ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
isCacheRW = true
|
||||||
|
}
|
||||||
|
cache.gasPerVoteCache[cs[i].Key] = *tmp
|
||||||
|
|
||||||
ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp))
|
ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if n.gasPerBlockChanged.Load().(bool) {
|
|
||||||
n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(ic.DAO))
|
|
||||||
n.gasPerBlockChanged.Store(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.registerPriceChanged.Load().(bool) {
|
|
||||||
p := getIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice})
|
|
||||||
n.registerPrice.Store(p)
|
|
||||||
n.registerPriceChanged.Store(false)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,28 +538,25 @@ func (n *NEO) getSortedGASRecordFromDAO(d *dao.Simple) gasRecord {
|
||||||
|
|
||||||
// GetGASPerBlock returns gas generated for block with provided index.
|
// GetGASPerBlock returns gas generated for block with provided index.
|
||||||
func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int {
|
func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int {
|
||||||
var gr gasRecord
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
if n.gasPerBlockChanged.Load().(bool) {
|
gr := cache.gasPerBlock
|
||||||
gr = n.getSortedGASRecordFromDAO(d)
|
|
||||||
} else {
|
|
||||||
gr = n.gasPerBlock.Load().(gasRecord)
|
|
||||||
}
|
|
||||||
for i := len(gr) - 1; i >= 0; i-- {
|
for i := len(gr) - 1; i >= 0; i-- {
|
||||||
if gr[i].Index <= index {
|
if gr[i].Index <= index {
|
||||||
g := gr[i].GASPerBlock
|
g := gr[i].GASPerBlock
|
||||||
return &g
|
return &g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("contract not initialized")
|
panic("NEO cache not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitteeAddress returns address of the committee.
|
// GetCommitteeAddress returns address of the committee.
|
||||||
func (n *NEO) GetCommitteeAddress() util.Uint160 {
|
func (n *NEO) GetCommitteeAddress(d *dao.Simple) util.Uint160 {
|
||||||
return n.committeeHash.Load().(util.Uint160)
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
|
return cache.committeeHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) checkCommittee(ic *interop.Context) bool {
|
func (n *NEO) checkCommittee(ic *interop.Context) bool {
|
||||||
ok, err := runtime.CheckHashedWitness(ic, n.GetCommitteeAddress())
|
ok, err := runtime.CheckHashedWitness(ic, n.GetCommitteeAddress(ic.DAO))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -549,8 +580,12 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) er
|
||||||
if !n.checkCommittee(ic) {
|
if !n.checkCommittee(ic) {
|
||||||
return errors.New("invalid committee signature")
|
return errors.New("invalid committee signature")
|
||||||
}
|
}
|
||||||
n.gasPerBlockChanged.Store(true)
|
|
||||||
n.putGASRecord(ic.DAO, index, gas)
|
n.putGASRecord(ic.DAO, index, gas)
|
||||||
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
cache.gasPerBlock = append(cache.gasPerBlock, gasIndexPair{
|
||||||
|
Index: index,
|
||||||
|
GASPerBlock: *gas,
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,10 +594,8 @@ func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackite
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 {
|
func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 {
|
||||||
if !n.registerPriceChanged.Load().(bool) {
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
return n.registerPrice.Load().(int64)
|
return cache.registerPrice
|
||||||
}
|
|
||||||
return getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
@ -575,13 +608,14 @@ func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stack
|
||||||
}
|
}
|
||||||
|
|
||||||
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, price.Int64())
|
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, price.Int64())
|
||||||
n.registerPriceChanged.Store(true)
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
cache.registerPrice = price.Int64()
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) dropCandidateIfZero(d *dao.Simple, pub *keys.PublicKey, c *candidate) (bool, error) {
|
func (n *NEO) dropCandidateIfZero(d *dao.Simple, cache *NeoCache, pub *keys.PublicKey, c *candidate) bool {
|
||||||
if c.Registered || c.Votes.Sign() != 0 {
|
if c.Registered || c.Votes.Sign() != 0 {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
d.DeleteStorageItem(n.ID, makeValidatorKey(pub))
|
d.DeleteStorageItem(n.ID, makeValidatorKey(pub))
|
||||||
|
|
||||||
|
@ -590,9 +624,9 @@ func (n *NEO) dropCandidateIfZero(d *dao.Simple, pub *keys.PublicKey, c *candida
|
||||||
d.DeleteStorageItem(n.ID, append(voterKey, k...)) // d.Seek cuts prefix, thus need to append it again.
|
d.DeleteStorageItem(n.ID, append(voterKey, k...)) // d.Seek cuts prefix, thus need to append it again.
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
delete(n.gasPerVoteCache, string(voterKey))
|
delete(cache.gasPerVoteCache, string(voterKey))
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVoterKey(pub []byte, prealloc ...[]byte) []byte {
|
func makeVoterKey(pub []byte, prealloc ...[]byte) []byte {
|
||||||
|
@ -644,12 +678,8 @@ func (n *NEO) CalculateNEOHolderReward(d *dao.Simple, value *big.Int, start, end
|
||||||
} else if value.Sign() < 0 {
|
} else if value.Sign() < 0 {
|
||||||
return nil, errors.New("negative value")
|
return nil, errors.New("negative value")
|
||||||
}
|
}
|
||||||
var gr gasRecord
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
if !n.gasPerBlockChanged.Load().(bool) {
|
gr := cache.gasPerBlock
|
||||||
gr = n.gasPerBlock.Load().(gasRecord)
|
|
||||||
} else {
|
|
||||||
gr = n.getSortedGASRecordFromDAO(d)
|
|
||||||
}
|
|
||||||
var sum, tmp big.Int
|
var sum, tmp big.Int
|
||||||
for i := len(gr) - 1; i >= 0; i-- {
|
for i := len(gr) - 1; i >= 0; i-- {
|
||||||
if gr[i].Index >= end {
|
if gr[i].Index >= end {
|
||||||
|
@ -719,12 +749,13 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
|
||||||
if si == nil {
|
if si == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
n.validators.Store(keys.PublicKeys(nil))
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
cache.validators = nil
|
||||||
c := new(candidate).FromBytes(si)
|
c := new(candidate).FromBytes(si)
|
||||||
c.Registered = false
|
c.Registered = false
|
||||||
ok, err := n.dropCandidateIfZero(ic.DAO, pub, c)
|
ok := n.dropCandidateIfZero(ic.DAO, cache, pub, c)
|
||||||
if ok {
|
if ok {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
return putConvertibleToDAO(n.ID, ic.DAO, key, c)
|
return putConvertibleToDAO(n.ID, ic.DAO, key, c)
|
||||||
}
|
}
|
||||||
|
@ -798,7 +829,8 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
|
||||||
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
|
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
|
||||||
// typ specifies if this modify is occurring during transfer or vote (with old or new validator).
|
// typ specifies if this modify is occurring during transfer or vote (with old or new validator).
|
||||||
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error {
|
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error {
|
||||||
n.votesChanged.Store(true)
|
cache := d.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
cache.votesChanged = true
|
||||||
if acc.VoteTo != nil {
|
if acc.VoteTo != nil {
|
||||||
key := makeValidatorKey(acc.VoteTo)
|
key := makeValidatorKey(acc.VoteTo)
|
||||||
si := d.GetStorageItem(n.ID, key)
|
si := d.GetStorageItem(n.ID, key)
|
||||||
|
@ -808,12 +840,12 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *bi
|
||||||
cd := new(candidate).FromBytes(si)
|
cd := new(candidate).FromBytes(si)
|
||||||
cd.Votes.Add(&cd.Votes, value)
|
cd.Votes.Add(&cd.Votes, value)
|
||||||
if !isNewVote {
|
if !isNewVote {
|
||||||
ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd)
|
ok := n.dropCandidateIfZero(d, cache, acc.VoteTo, cd)
|
||||||
if ok {
|
if ok {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.validators.Store(keys.PublicKeys(nil))
|
cache.validators = nil
|
||||||
return putConvertibleToDAO(n.ID, d, key, cd)
|
return putConvertibleToDAO(n.ID, d, key, cd)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -825,7 +857,7 @@ func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool) ([]keyWithVotes, erro
|
||||||
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool {
|
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool {
|
||||||
c := new(candidate).FromBytes(v)
|
c := new(candidate).FromBytes(v)
|
||||||
emit.CheckSig(buf.BinWriter, k)
|
emit.CheckSig(buf.BinWriter, k)
|
||||||
if c.Registered && !n.Policy.IsBlockedInternal(d, hash.Hash160(buf.Bytes())) {
|
if c.Registered && !n.Policy.IsBlocked(d, hash.Hash160(buf.Bytes())) {
|
||||||
arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes})
|
arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes})
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
@ -906,23 +938,27 @@ func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stacki
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputeNextBlockValidators returns an actual list of current validators.
|
// ComputeNextBlockValidators returns an actual list of current validators.
|
||||||
func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, error) {
|
func (n *NEO) ComputeNextBlockValidators(blockHeight uint32, d *dao.Simple) (keys.PublicKeys, error) {
|
||||||
numOfCNs := n.cfg.GetNumOfCNs(bc.BlockHeight() + 1)
|
numOfCNs := n.cfg.GetNumOfCNs(blockHeight + 1)
|
||||||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil && numOfCNs == len(vals) {
|
// Most of the time it should be OK with RO cache, thus try to retrieve
|
||||||
|
// validators without RW cache creation to avoid cached values copying.
|
||||||
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
|
if vals := cache.validators; vals != nil && numOfCNs == len(vals) {
|
||||||
return vals.Copy(), nil
|
return vals.Copy(), nil
|
||||||
}
|
}
|
||||||
result, _, err := n.computeCommitteeMembers(bc, d)
|
cache = d.GetRWCache(n.ID).(*NeoCache)
|
||||||
|
result, _, err := n.computeCommitteeMembers(blockHeight, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = result[:numOfCNs]
|
result = result[:numOfCNs]
|
||||||
sort.Sort(result)
|
sort.Sort(result)
|
||||||
n.validators.Store(result)
|
cache.validators = result
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
pubs := n.GetCommitteeMembers()
|
pubs := n.GetCommitteeMembers(ic.DAO)
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
return pubsToArray(pubs)
|
return pubsToArray(pubs)
|
||||||
}
|
}
|
||||||
|
@ -941,8 +977,13 @@ func (n *NEO) modifyVoterTurnout(d *dao.Simple, amount *big.Int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
||||||
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
|
func (n *NEO) GetCommitteeMembers(d *dao.Simple) keys.PublicKeys {
|
||||||
var cvs = n.committee.Load().(keysWithVotes)
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
|
return getCommitteeMembers(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommitteeMembers(cache *NeoCache) keys.PublicKeys {
|
||||||
|
var cvs = cache.committee
|
||||||
var committee = make(keys.PublicKeys, len(cvs))
|
var committee = make(keys.PublicKeys, len(cvs))
|
||||||
var err error
|
var err error
|
||||||
for i := range committee {
|
for i := range committee {
|
||||||
|
@ -965,7 +1006,7 @@ func toKeysWithVotes(pubs keys.PublicKeys) keysWithVotes {
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeCommitteeMembers returns public keys of nodes in committee.
|
// computeCommitteeMembers returns public keys of nodes in committee.
|
||||||
func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, keysWithVotes, error) {
|
func (n *NEO) computeCommitteeMembers(blockHeight uint32, d *dao.Simple) (keys.PublicKeys, keysWithVotes, error) {
|
||||||
key := []byte{prefixVotersCount}
|
key := []byte{prefixVotersCount}
|
||||||
si := d.GetStorageItem(n.ID, key)
|
si := d.GetStorageItem(n.ID, key)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
|
@ -977,7 +1018,7 @@ func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d *dao.Simple) (keys.Pu
|
||||||
_, totalSupply := n.getTotalSupply(d)
|
_, totalSupply := n.getTotalSupply(d)
|
||||||
voterTurnout := votersCount.Div(votersCount, totalSupply)
|
voterTurnout := votersCount.Div(votersCount, totalSupply)
|
||||||
|
|
||||||
count := n.cfg.GetCommitteeSize(bc.BlockHeight() + 1)
|
count := n.cfg.GetCommitteeSize(blockHeight + 1)
|
||||||
// Can be sorted and/or returned to outside users, thus needs to be copied.
|
// Can be sorted and/or returned to outside users, thus needs to be copied.
|
||||||
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
||||||
cs, err := n.getCandidates(d, false)
|
cs, err := n.getCandidates(d, false)
|
||||||
|
@ -1011,13 +1052,14 @@ func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d *dao.Simple) (keys.Pu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
result := n.GetNextBlockValidatorsInternal()
|
result := n.GetNextBlockValidatorsInternal(ic.DAO)
|
||||||
return pubsToArray(result)
|
return pubsToArray(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextBlockValidatorsInternal returns next block validators.
|
// GetNextBlockValidatorsInternal returns next block validators.
|
||||||
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
|
func (n *NEO) GetNextBlockValidatorsInternal(d *dao.Simple) keys.PublicKeys {
|
||||||
return n.nextValidators.Load().(keys.PublicKeys).Copy()
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||||
|
return cache.nextValidators.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceOf returns native NEO token balance for the acc.
|
// BalanceOf returns native NEO token balance for the acc.
|
||||||
|
|
|
@ -8,9 +8,12 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -77,6 +80,38 @@ func testGetSet(t *testing.T, c *neotest.ContractInvoker, name string, defaultVa
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGetSetCache(t *testing.T, c *neotest.ContractInvoker, name string, defaultValue int64) {
|
||||||
|
getName := "get" + name
|
||||||
|
setName := "set" + name
|
||||||
|
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
newVal := defaultValue - 1
|
||||||
|
|
||||||
|
// Change fee, abort the transaction and check that contract cache wasn't persisted
|
||||||
|
// for FAULTed tx at the same block.
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, committeeInvoker.Hash, setName, callflag.All, newVal)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
tx1 := committeeInvoker.PrepareInvocation(t, w.Bytes(), committeeInvoker.Signers)
|
||||||
|
tx2 := committeeInvoker.PrepareInvoke(t, getName)
|
||||||
|
committeeInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
committeeInvoker.CheckFault(t, tx1.Hash(), "ABORT")
|
||||||
|
committeeInvoker.CheckHalt(t, tx2.Hash(), stackitem.Make(defaultValue))
|
||||||
|
|
||||||
|
// Change fee and check that change is available for the next tx.
|
||||||
|
tx1 = committeeInvoker.PrepareInvoke(t, setName, newVal)
|
||||||
|
tx2 = committeeInvoker.PrepareInvoke(t, getName)
|
||||||
|
committeeInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
committeeInvoker.CheckHalt(t, tx1.Hash())
|
||||||
|
if name != "GasPerBlock" {
|
||||||
|
committeeInvoker.CheckHalt(t, tx2.Hash(), stackitem.Make(newVal))
|
||||||
|
} else {
|
||||||
|
committeeInvoker.CheckHalt(t, tx2.Hash(), stackitem.Make(defaultValue))
|
||||||
|
committeeInvoker.Invoke(t, newVal, getName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
|
func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
|
||||||
pubs := make([]interface{}, len(nodes))
|
pubs := make([]interface{}, len(nodes))
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
|
|
|
@ -5,8 +5,15 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,3 +52,88 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||||
checkNodeRoles(t, designateInvoker, true, noderoles.NeoFSAlphabet, e.Chain.BlockHeight()+1, pubs)
|
checkNodeRoles(t, designateInvoker, true, noderoles.NeoFSAlphabet, e.Chain.BlockHeight()+1, pubs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyOracle struct {
|
||||||
|
updateNodes func(k keys.PublicKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRequests processes new requests.
|
||||||
|
func (o *dummyOracle) AddRequests(map[uint64]*state.OracleRequest) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRequests removes already processed requests.
|
||||||
|
func (o *dummyOracle) RemoveRequests([]uint64) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOracleNodes updates oracle nodes.
|
||||||
|
func (o *dummyOracle) UpdateOracleNodes(k keys.PublicKeys) {
|
||||||
|
if o.updateNodes != nil {
|
||||||
|
o.updateNodes(k)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNativeContract updates oracle contract native script and hash.
|
||||||
|
func (o *dummyOracle) UpdateNativeContract([]byte, []byte, util.Uint160, int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs oracle module.
|
||||||
|
func (o *dummyOracle) Start() {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shutdowns oracle module.
|
||||||
|
func (o *dummyOracle) Shutdown() {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignate_Cache(t *testing.T) {
|
||||||
|
c := newDesignateClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
designateInvoker := c.WithSigners(c.Committee)
|
||||||
|
r := int64(noderoles.Oracle)
|
||||||
|
var (
|
||||||
|
updatedNodes keys.PublicKeys
|
||||||
|
updateCalled bool
|
||||||
|
)
|
||||||
|
oracleServ := &dummyOracle{
|
||||||
|
updateNodes: func(k keys.PublicKeys) {
|
||||||
|
updatedNodes = k
|
||||||
|
updateCalled = true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
privGood, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubsGood := []interface{}{privGood.PublicKey().Bytes()}
|
||||||
|
|
||||||
|
privBad, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubsBad := []interface{}{privBad.PublicKey().Bytes()}
|
||||||
|
|
||||||
|
// Firstly, designate good Oracle node and check that OracleService callback was called during PostPersist.
|
||||||
|
e.Chain.SetOracle(oracleServ)
|
||||||
|
txDesignateGood := designateInvoker.PrepareInvoke(t, "designateAsRole", r, pubsGood)
|
||||||
|
e.AddNewBlock(t, txDesignateGood)
|
||||||
|
e.CheckHalt(t, txDesignateGood.Hash(), stackitem.Null{})
|
||||||
|
require.True(t, updateCalled)
|
||||||
|
require.Equal(t, keys.PublicKeys{privGood.PublicKey()}, updatedNodes)
|
||||||
|
updatedNodes = nil
|
||||||
|
updateCalled = false
|
||||||
|
|
||||||
|
// Check designated node in a separate block.
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.Oracle, e.Chain.BlockHeight()+1, keys.PublicKeys{privGood.PublicKey()})
|
||||||
|
|
||||||
|
// Designate privBad as oracle node and abort the transaction. Designation cache changes
|
||||||
|
// shouldn't be persisted to the contract and no notification should be sent.
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, designateInvoker.Hash, "designateAsRole", callflag.All, int64(r), pubsBad)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
script := w.Bytes()
|
||||||
|
|
||||||
|
designateInvoker.InvokeScriptCheckFAULT(t, script, designateInvoker.Signers, "ABORT")
|
||||||
|
require.Nil(t, updatedNodes)
|
||||||
|
require.False(t, updateCalled)
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"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/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
@ -34,6 +36,43 @@ func TestManagement_MinimumDeploymentFee(t *testing.T) {
|
||||||
testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0)
|
testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestManagement_MinimumDeploymentFeeCache(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
testGetSetCache(t, c, "MinimumDeploymentFee", 10_00000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_ContractCache(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.Committee.ScriptHash())
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Deploy contract, abort the transaction and check that Management cache wasn't persisted
|
||||||
|
// for FAULTed tx at the same block.
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, managementInvoker.Hash, "deploy", callflag.All, nefBytes, manifestBytes)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
tx1 := managementInvoker.PrepareInvocation(t, w.Bytes(), managementInvoker.Signers)
|
||||||
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
||||||
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
managementInvoker.CheckFault(t, tx1.Hash(), "ABORT")
|
||||||
|
managementInvoker.CheckHalt(t, tx2.Hash(), stackitem.Null{})
|
||||||
|
|
||||||
|
// Deploy the contract and check that cache was persisted for HALTed transaction at the same block.
|
||||||
|
tx1 = managementInvoker.PrepareInvoke(t, "deploy", nefBytes, manifestBytes)
|
||||||
|
tx2 = managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
||||||
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
managementInvoker.CheckHalt(t, tx1.Hash())
|
||||||
|
aer, err := managementInvoker.Chain.GetAppExecResults(tx2.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||||
|
require.NotEqual(t, stackitem.Null{}, aer[0].Stack)
|
||||||
|
}
|
||||||
|
|
||||||
func TestManagement_ContractDeploy(t *testing.T) {
|
func TestManagement_ContractDeploy(t *testing.T) {
|
||||||
c := newManagementClient(t)
|
c := newManagementClient(t)
|
||||||
managementInvoker := c.WithSigners(c.Committee)
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
|
@ -42,10 +42,18 @@ func TestNEO_GasPerBlock(t *testing.T) {
|
||||||
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor, 0, 10*native.GASFactor)
|
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor, 0, 10*native.GASFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNEO_GasPerBlockCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNEO_RegisterPrice(t *testing.T) {
|
func TestNEO_RegisterPrice(t *testing.T) {
|
||||||
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice, 1, math.MaxInt64)
|
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice, 1, math.MaxInt64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNEO_RegisterPriceCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNEO_Vote(t *testing.T) {
|
func TestNEO_Vote(t *testing.T) {
|
||||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||||
|
|
|
@ -32,11 +32,21 @@ func TestNotary_MaxNotValidBeforeDelta(t *testing.T) {
|
||||||
testGetSet(t, c, "MaxNotValidBeforeDelta", 140, int64(c.Chain.GetConfig().ValidatorsCount), int64(c.Chain.GetConfig().MaxValidUntilBlockIncrement/2))
|
testGetSet(t, c, "MaxNotValidBeforeDelta", 140, int64(c.Chain.GetConfig().ValidatorsCount), int64(c.Chain.GetConfig().MaxValidUntilBlockIncrement/2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotary_MaxNotValidBeforeDeltaCache(t *testing.T) {
|
||||||
|
c := newNotaryClient(t)
|
||||||
|
testGetSetCache(t, c, "MaxNotValidBeforeDelta", 140)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNotary_NotaryServiceFeePerKey(t *testing.T) {
|
func TestNotary_NotaryServiceFeePerKey(t *testing.T) {
|
||||||
c := newNotaryClient(t)
|
c := newNotaryClient(t)
|
||||||
testGetSet(t, c, "NotaryServiceFeePerKey", 1000_0000, 0, 0)
|
testGetSet(t, c, "NotaryServiceFeePerKey", 1000_0000, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotary_NotaryServiceFeePerKeyCache(t *testing.T) {
|
||||||
|
c := newNotaryClient(t)
|
||||||
|
testGetSetCache(t, c, "NotaryServiceFeePerKey", 1000_0000)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNotary_Pipeline(t *testing.T) {
|
func TestNotary_Pipeline(t *testing.T) {
|
||||||
notaryCommitteeInvoker := newNotaryClient(t)
|
notaryCommitteeInvoker := newNotaryClient(t)
|
||||||
e := notaryCommitteeInvoker.Executor
|
e := notaryCommitteeInvoker.Executor
|
||||||
|
|
|
@ -28,10 +28,14 @@ func newOracleClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
return newNativeClient(t, nativenames.Oracle)
|
return newNativeClient(t, nativenames.Oracle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSetPrice(t *testing.T) {
|
func TestOracle_GetSetPrice(t *testing.T) {
|
||||||
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
|
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOracle_GetSetPriceCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice)
|
||||||
|
}
|
||||||
|
|
||||||
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
|
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
|
||||||
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
|
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
|
||||||
var filtItem interface{}
|
var filtItem interface{}
|
||||||
|
|
|
@ -19,14 +19,26 @@ func TestPolicy_FeePerByte(t *testing.T) {
|
||||||
testGetSet(t, newPolicyClient(t), "FeePerByte", 1000, 0, 100_000_000)
|
testGetSet(t, newPolicyClient(t), "FeePerByte", 1000, 0, 100_000_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicy_FeePerByteCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newPolicyClient(t), "FeePerByte", 1000)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolicy_ExecFeeFactor(t *testing.T) {
|
func TestPolicy_ExecFeeFactor(t *testing.T) {
|
||||||
testGetSet(t, newPolicyClient(t), "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000)
|
testGetSet(t, newPolicyClient(t), "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicy_ExecFeeFactorCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newPolicyClient(t), "ExecFeeFactor", interop.DefaultBaseExecFee)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolicy_StoragePrice(t *testing.T) {
|
func TestPolicy_StoragePrice(t *testing.T) {
|
||||||
testGetSet(t, newPolicyClient(t), "StoragePrice", native.DefaultStoragePrice, 1, 10000000)
|
testGetSet(t, newPolicyClient(t), "StoragePrice", native.DefaultStoragePrice, 1, 10000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicy_StoragePriceCache(t *testing.T) {
|
||||||
|
testGetSetCache(t, newPolicyClient(t), "StoragePrice", native.DefaultStoragePrice)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolicy_BlockedAccounts(t *testing.T) {
|
func TestPolicy_BlockedAccounts(t *testing.T) {
|
||||||
c := newPolicyClient(t)
|
c := newPolicyClient(t)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"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/interop"
|
||||||
|
@ -32,12 +31,9 @@ type Notary struct {
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
Desig *Designate
|
Desig *Designate
|
||||||
|
}
|
||||||
|
|
||||||
lock sync.RWMutex
|
type NotaryCache struct {
|
||||||
// isValid defies whether cached values were changed during the current
|
|
||||||
// consensus iteration. If false, these values will be updated after
|
|
||||||
// blockchain DAO persisting. If true, we can safely use cached values.
|
|
||||||
isValid bool
|
|
||||||
maxNotValidBeforeDelta uint32
|
maxNotValidBeforeDelta uint32
|
||||||
notaryServiceFeePerKey int64
|
notaryServiceFeePerKey int64
|
||||||
}
|
}
|
||||||
|
@ -56,6 +52,22 @@ var (
|
||||||
notaryServiceFeeKey = []byte{5}
|
notaryServiceFeeKey = []byte{5}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ interop.Contract = (*Notary)(nil)
|
||||||
|
_ dao.NativeContractCache = (*NotaryCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *NotaryCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &NotaryCache{}
|
||||||
|
copyNotaryCache(c, cp)
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyNotaryCache(src, dst *NotaryCache) {
|
||||||
|
*dst = *src
|
||||||
|
}
|
||||||
|
|
||||||
// newNotary returns Notary native contract.
|
// newNotary returns Notary native contract.
|
||||||
func newNotary() *Notary {
|
func newNotary() *Notary {
|
||||||
n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)}
|
n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)}
|
||||||
|
@ -125,9 +137,22 @@ func (n *Notary) Metadata() *interop.ContractMD {
|
||||||
func (n *Notary) Initialize(ic *interop.Context) error {
|
func (n *Notary) Initialize(ic *interop.Context) error {
|
||||||
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
|
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
|
||||||
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey)
|
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey)
|
||||||
n.isValid = true
|
|
||||||
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta
|
cache := &NotaryCache{
|
||||||
n.notaryServiceFeePerKey = defaultNotaryServiceFeePerKey
|
maxNotValidBeforeDelta: defaultMaxNotValidBeforeDelta,
|
||||||
|
notaryServiceFeePerKey: defaultNotaryServiceFeePerKey,
|
||||||
|
}
|
||||||
|
ic.DAO.SetCache(n.ID, cache)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notary) InitializeCache(d *dao.Simple) error {
|
||||||
|
cache := &NotaryCache{
|
||||||
|
maxNotValidBeforeDelta: uint32(getIntWithKey(n.ID, d, maxNotValidBeforeDeltaKey)),
|
||||||
|
notaryServiceFeePerKey: getIntWithKey(n.ID, d, notaryServiceFeeKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetCache(n.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,15 +201,6 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
||||||
|
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (n *Notary) PostPersist(ic *interop.Context) error {
|
func (n *Notary) PostPersist(ic *interop.Context) error {
|
||||||
n.lock.Lock()
|
|
||||||
defer n.lock.Unlock()
|
|
||||||
if n.isValid {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
n.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey))
|
|
||||||
n.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey)
|
|
||||||
n.isValid = true
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +223,7 @@ func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedChangeTill := ic.Tx.Sender() == to
|
allowedChangeTill := ic.Tx.Sender() == to
|
||||||
currentHeight := ic.Chain.BlockHeight()
|
currentHeight := ic.BlockHeight()
|
||||||
deposit := n.GetDepositFor(ic.DAO, to)
|
deposit := n.GetDepositFor(ic.DAO, to)
|
||||||
till := toUint32(additionalParams[1])
|
till := toUint32(additionalParams[1])
|
||||||
if till < currentHeight {
|
if till < currentHeight {
|
||||||
|
@ -250,7 +266,7 @@ func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) st
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
till := toUint32(args[1])
|
till := toUint32(args[1])
|
||||||
if till < ic.Chain.BlockHeight() {
|
if till < ic.BlockHeight() {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
deposit := n.GetDepositFor(ic.DAO, addr)
|
deposit := n.GetDepositFor(ic.DAO, addr)
|
||||||
|
@ -286,7 +302,7 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.
|
||||||
if deposit == nil {
|
if deposit == nil {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
if ic.Chain.BlockHeight() < deposit.Till {
|
if ic.BlockHeight() < deposit.Till {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
cs, err := ic.GetContract(n.GAS.Hash)
|
cs, err := ic.GetContract(n.GAS.Hash)
|
||||||
|
@ -391,12 +407,8 @@ func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.It
|
||||||
|
|
||||||
// GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method.
|
// GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method.
|
||||||
func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 {
|
func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 {
|
||||||
n.lock.RLock()
|
cache := dao.GetROCache(n.ID).(*NotaryCache)
|
||||||
defer n.lock.RUnlock()
|
return cache.maxNotValidBeforeDelta
|
||||||
if n.isValid {
|
|
||||||
return n.maxNotValidBeforeDelta
|
|
||||||
}
|
|
||||||
return uint32(getIntWithKey(n.ID, dao, maxNotValidBeforeDeltaKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setMaxNotValidBeforeDelta is Notary contract method and sets the maximum NotValidBefore delta.
|
// setMaxNotValidBeforeDelta is Notary contract method and sets the maximum NotValidBefore delta.
|
||||||
|
@ -404,16 +416,15 @@ func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem
|
||||||
value := toUint32(args[0])
|
value := toUint32(args[0])
|
||||||
cfg := ic.Chain.GetConfig()
|
cfg := ic.Chain.GetConfig()
|
||||||
maxInc := cfg.MaxValidUntilBlockIncrement
|
maxInc := cfg.MaxValidUntilBlockIncrement
|
||||||
if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.Chain.BlockHeight())) {
|
if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.BlockHeight())) {
|
||||||
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, cfg.GetNumOfCNs(ic.Chain.BlockHeight())))
|
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, cfg.GetNumOfCNs(ic.BlockHeight())))
|
||||||
}
|
}
|
||||||
if !n.NEO.checkCommittee(ic) {
|
if !n.NEO.checkCommittee(ic) {
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
n.lock.Lock()
|
|
||||||
defer n.lock.Unlock()
|
|
||||||
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value))
|
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value))
|
||||||
n.isValid = false
|
cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache)
|
||||||
|
cache.maxNotValidBeforeDelta = value
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,12 +435,8 @@ func (n *Notary) getNotaryServiceFeePerKey(ic *interop.Context, _ []stackitem.It
|
||||||
|
|
||||||
// GetNotaryServiceFeePerKey is an internal representation of Notary getNotaryServiceFeePerKey method.
|
// GetNotaryServiceFeePerKey is an internal representation of Notary getNotaryServiceFeePerKey method.
|
||||||
func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 {
|
func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 {
|
||||||
n.lock.RLock()
|
cache := dao.GetROCache(n.ID).(*NotaryCache)
|
||||||
defer n.lock.RUnlock()
|
return cache.notaryServiceFeePerKey
|
||||||
if n.isValid {
|
|
||||||
return n.notaryServiceFeePerKey
|
|
||||||
}
|
|
||||||
return getIntWithKey(n.ID, dao, notaryServiceFeeKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setNotaryServiceFeePerKey is Notary contract method and sets a reward per notary request key for notary nodes.
|
// setNotaryServiceFeePerKey is Notary contract method and sets a reward per notary request key for notary nodes.
|
||||||
|
@ -441,10 +448,9 @@ func (n *Notary) setNotaryServiceFeePerKey(ic *interop.Context, args []stackitem
|
||||||
if !n.NEO.checkCommittee(ic) {
|
if !n.NEO.checkCommittee(ic) {
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
n.lock.Lock()
|
|
||||||
defer n.lock.Unlock()
|
|
||||||
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value))
|
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value))
|
||||||
n.isValid = false
|
cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache)
|
||||||
|
cache.notaryServiceFeePerKey = value
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,15 +40,16 @@ type Oracle struct {
|
||||||
Desig *Designate
|
Desig *Designate
|
||||||
oracleScript []byte
|
oracleScript []byte
|
||||||
|
|
||||||
requestPrice atomic.Value
|
|
||||||
requestPriceChanged atomic.Value
|
|
||||||
|
|
||||||
// Module is an oracle module capable of talking with the external world.
|
// Module is an oracle module capable of talking with the external world.
|
||||||
Module atomic.Value
|
Module atomic.Value
|
||||||
// newRequests contains new requests created during current block.
|
// newRequests contains new requests created during current block.
|
||||||
newRequests map[uint64]*state.OracleRequest
|
newRequests map[uint64]*state.OracleRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OracleCache struct {
|
||||||
|
requestPrice int64
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
oracleContractID = -9
|
oracleContractID = -9
|
||||||
maxURLLength = 256
|
maxURLLength = 256
|
||||||
|
@ -82,6 +83,22 @@ var (
|
||||||
ErrResponseNotFound = errors.New("oracle response not found")
|
ErrResponseNotFound = errors.New("oracle response not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ interop.Contract = (*Oracle)(nil)
|
||||||
|
_ dao.NativeContractCache = (*OracleCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *OracleCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &OracleCache{}
|
||||||
|
copyOracleCache(c, cp)
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyOracleCache(src, dst *OracleCache) {
|
||||||
|
*dst = *src
|
||||||
|
}
|
||||||
|
|
||||||
func newOracle() *Oracle {
|
func newOracle() *Oracle {
|
||||||
o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)}
|
o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)}
|
||||||
defer o.UpdateHash()
|
defer o.UpdateHash()
|
||||||
|
@ -121,8 +138,6 @@ func newOracle() *Oracle {
|
||||||
md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States)
|
md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States)
|
||||||
o.AddMethod(md, desc)
|
o.AddMethod(md, desc)
|
||||||
|
|
||||||
o.requestPriceChanged.Store(true)
|
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,10 +158,6 @@ func (o *Oracle) OnPersist(ic *interop.Context) error {
|
||||||
// PostPersist represents `postPersist` method.
|
// PostPersist represents `postPersist` method.
|
||||||
func (o *Oracle) PostPersist(ic *interop.Context) error {
|
func (o *Oracle) PostPersist(ic *interop.Context) error {
|
||||||
p := o.getPriceInternal(ic.DAO)
|
p := o.getPriceInternal(ic.DAO)
|
||||||
if o.requestPriceChanged.Load().(bool) {
|
|
||||||
o.requestPrice.Store(p)
|
|
||||||
o.requestPriceChanged.Store(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodes keys.PublicKeys
|
var nodes keys.PublicKeys
|
||||||
var reward []big.Int
|
var reward []big.Int
|
||||||
|
@ -220,11 +231,20 @@ func (o *Oracle) Metadata() *interop.ContractMD {
|
||||||
func (o *Oracle) Initialize(ic *interop.Context) error {
|
func (o *Oracle) Initialize(ic *interop.Context) error {
|
||||||
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
|
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
|
||||||
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice)
|
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice)
|
||||||
o.requestPrice.Store(int64(DefaultOracleRequestPrice))
|
|
||||||
o.requestPriceChanged.Store(false)
|
cache := &OracleCache{
|
||||||
|
requestPrice: int64(DefaultOracleRequestPrice),
|
||||||
|
}
|
||||||
|
ic.DAO.SetCache(o.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Oracle) InitializeCache(d *dao.Simple) {
|
||||||
|
cache := &OracleCache{}
|
||||||
|
cache.requestPrice = getIntWithKey(o.ID, d, prefixRequestPrice)
|
||||||
|
d.SetCache(o.ID, cache)
|
||||||
|
}
|
||||||
|
|
||||||
func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
|
func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
|
||||||
for i := range tx.Attributes {
|
for i := range tx.Attributes {
|
||||||
if tx.Attributes[i].Type == transaction.OracleResponseT {
|
if tx.Attributes[i].Type == transaction.OracleResponseT {
|
||||||
|
@ -439,10 +459,8 @@ func (o *Oracle) getPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Ite
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oracle) getPriceInternal(d *dao.Simple) int64 {
|
func (o *Oracle) getPriceInternal(d *dao.Simple) int64 {
|
||||||
if !o.requestPriceChanged.Load().(bool) {
|
cache := d.GetROCache(o.ID).(*OracleCache)
|
||||||
return o.requestPrice.Load().(int64)
|
return cache.requestPrice
|
||||||
}
|
|
||||||
return getIntWithKey(o.ID, d, prefixRequestPrice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
@ -454,7 +472,8 @@ func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem.
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64())
|
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64())
|
||||||
o.requestPriceChanged.Store(true)
|
cache := ic.DAO.GetRWCache(o.ID).(*OracleCache)
|
||||||
|
cache.requestPrice = price.Int64()
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"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/interop"
|
||||||
|
@ -52,12 +51,10 @@ var (
|
||||||
// Policy represents Policy native contract.
|
// Policy represents Policy native contract.
|
||||||
type Policy struct {
|
type Policy struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
NEO *NEO
|
NEO *NEO
|
||||||
lock sync.RWMutex
|
}
|
||||||
// isValid defies whether cached values were changed during the current
|
|
||||||
// consensus iteration. If false, these values will be updated after
|
type PolicyCache struct {
|
||||||
// blockchain DAO persisting. If true, we can safely use cached values.
|
|
||||||
isValid bool
|
|
||||||
execFeeFactor uint32
|
execFeeFactor uint32
|
||||||
feePerByte int64
|
feePerByte int64
|
||||||
maxVerificationGas int64
|
maxVerificationGas int64
|
||||||
|
@ -65,7 +62,23 @@ type Policy struct {
|
||||||
blockedAccounts []util.Uint160
|
blockedAccounts []util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ interop.Contract = (*Policy)(nil)
|
var (
|
||||||
|
_ interop.Contract = (*Policy)(nil)
|
||||||
|
_ dao.NativeContractCache = (*PolicyCache)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copy implements NativeContractCache interface.
|
||||||
|
func (c *PolicyCache) Copy() dao.NativeContractCache {
|
||||||
|
cp := &PolicyCache{}
|
||||||
|
copyPolicyCache(c, cp)
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyPolicyCache(src, dst *PolicyCache) {
|
||||||
|
*dst = *src
|
||||||
|
dst.blockedAccounts = make([]util.Uint160, len(src.blockedAccounts))
|
||||||
|
copy(dst.blockedAccounts, src.blockedAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
// newPolicy returns Policy native contract.
|
// newPolicy returns Policy native contract.
|
||||||
func newPolicy() *Policy {
|
func newPolicy() *Policy {
|
||||||
|
@ -128,16 +141,51 @@ func (p *Policy) Initialize(ic *interop.Context) error {
|
||||||
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
|
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
|
||||||
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
|
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
|
||||||
|
|
||||||
p.isValid = true
|
cache := &PolicyCache{
|
||||||
p.execFeeFactor = defaultExecFeeFactor
|
execFeeFactor: defaultExecFeeFactor,
|
||||||
p.feePerByte = defaultFeePerByte
|
feePerByte: defaultFeePerByte,
|
||||||
p.maxVerificationGas = defaultMaxVerificationGas
|
maxVerificationGas: defaultMaxVerificationGas,
|
||||||
p.storagePrice = DefaultStoragePrice
|
storagePrice: DefaultStoragePrice,
|
||||||
p.blockedAccounts = make([]util.Uint160, 0)
|
blockedAccounts: make([]util.Uint160, 0),
|
||||||
|
}
|
||||||
|
ic.DAO.SetCache(p.ID, cache)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Policy) InitializeCache(d *dao.Simple) error {
|
||||||
|
cache := &PolicyCache{}
|
||||||
|
err := p.fillCacheFromDAO(cache, d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.SetCache(p.ID, cache)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) fillCacheFromDAO(cache *PolicyCache, d *dao.Simple) error {
|
||||||
|
cache.execFeeFactor = uint32(getIntWithKey(p.ID, d, execFeeFactorKey))
|
||||||
|
cache.feePerByte = getIntWithKey(p.ID, d, feePerByteKey)
|
||||||
|
cache.maxVerificationGas = defaultMaxVerificationGas
|
||||||
|
cache.storagePrice = uint32(getIntWithKey(p.ID, d, storagePriceKey))
|
||||||
|
|
||||||
|
cache.blockedAccounts = make([]util.Uint160, 0)
|
||||||
|
var fErr error
|
||||||
|
d.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool {
|
||||||
|
hash, err := util.Uint160DecodeBytesBE(k)
|
||||||
|
if err != nil {
|
||||||
|
fErr = fmt.Errorf("failed to decode blocked account hash: %w", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cache.blockedAccounts = append(cache.blockedAccounts, hash)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if fErr != nil {
|
||||||
|
return fmt.Errorf("failed to initialize blocked accounts: %w", fErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// OnPersist implements Contract interface.
|
// OnPersist implements Contract interface.
|
||||||
func (p *Policy) OnPersist(ic *interop.Context) error {
|
func (p *Policy) OnPersist(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -145,32 +193,7 @@ func (p *Policy) OnPersist(ic *interop.Context) error {
|
||||||
|
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (p *Policy) PostPersist(ic *interop.Context) error {
|
func (p *Policy) PostPersist(ic *interop.Context) error {
|
||||||
p.lock.Lock()
|
return nil
|
||||||
defer p.lock.Unlock()
|
|
||||||
if p.isValid {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.execFeeFactor = uint32(getIntWithKey(p.ID, ic.DAO, execFeeFactorKey))
|
|
||||||
p.feePerByte = getIntWithKey(p.ID, ic.DAO, feePerByteKey)
|
|
||||||
p.maxVerificationGas = defaultMaxVerificationGas
|
|
||||||
p.storagePrice = uint32(getIntWithKey(p.ID, ic.DAO, storagePriceKey))
|
|
||||||
|
|
||||||
p.blockedAccounts = make([]util.Uint160, 0)
|
|
||||||
var fErr error
|
|
||||||
ic.DAO.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool {
|
|
||||||
hash, err := util.Uint160DecodeBytesBE(k)
|
|
||||||
if err != nil {
|
|
||||||
fErr = fmt.Errorf("failed to decode blocked account hash: %w", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
p.blockedAccounts = append(p.blockedAccounts, hash)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if fErr == nil {
|
|
||||||
p.isValid = true
|
|
||||||
}
|
|
||||||
return fErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFeePerByte is Policy contract method and returns required transaction's fee
|
// getFeePerByte is Policy contract method and returns required transaction's fee
|
||||||
|
@ -181,20 +204,14 @@ func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackite
|
||||||
|
|
||||||
// GetFeePerByteInternal returns required transaction's fee per byte.
|
// GetFeePerByteInternal returns required transaction's fee per byte.
|
||||||
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
|
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
|
||||||
p.lock.RLock()
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||||
defer p.lock.RUnlock()
|
return cache.feePerByte
|
||||||
if p.isValid {
|
|
||||||
return p.feePerByte
|
|
||||||
}
|
|
||||||
return getIntWithKey(p.ID, dao, feePerByteKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
|
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
|
||||||
func (p *Policy) GetMaxVerificationGas(_ *dao.Simple) int64 {
|
func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 {
|
||||||
if p.isValid {
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||||
return p.maxVerificationGas
|
return cache.maxVerificationGas
|
||||||
}
|
|
||||||
return defaultMaxVerificationGas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
@ -203,12 +220,8 @@ func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stack
|
||||||
|
|
||||||
// GetExecFeeFactorInternal returns current execution fee factor.
|
// GetExecFeeFactorInternal returns current execution fee factor.
|
||||||
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
|
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
|
||||||
p.lock.RLock()
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
||||||
defer p.lock.RUnlock()
|
return int64(cache.execFeeFactor)
|
||||||
if p.isValid {
|
|
||||||
return int64(p.execFeeFactor)
|
|
||||||
}
|
|
||||||
return getIntWithKey(p.ID, d, execFeeFactorKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
@ -219,35 +232,38 @@ func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) st
|
||||||
if !p.NEO.checkCommittee(ic) {
|
if !p.NEO.checkCommittee(ic) {
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
|
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
|
||||||
p.isValid = false
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||||
|
cache.execFeeFactor = value
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBlocked is Policy contract method and checks whether provided account is blocked.
|
// isBlocked is Policy contract method and checks whether provided account is blocked.
|
||||||
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
hash := toUint160(args[0])
|
hash := toUint160(args[0])
|
||||||
return stackitem.NewBool(p.IsBlockedInternal(ic.DAO, hash))
|
_, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||||
|
return stackitem.NewBool(blocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBlockedInternal checks whether provided account is blocked.
|
// IsBlocked checks whether provided account is blocked.
|
||||||
func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool {
|
func (p *Policy) IsBlocked(dao *dao.Simple, hash util.Uint160) bool {
|
||||||
p.lock.RLock()
|
_, isBlocked := p.isBlockedInternal(dao, hash)
|
||||||
defer p.lock.RUnlock()
|
return isBlocked
|
||||||
if p.isValid {
|
}
|
||||||
length := len(p.blockedAccounts)
|
|
||||||
i := sort.Search(length, func(i int) bool {
|
// isBlockedInternal checks whether provided account is blocked. It returns position
|
||||||
return !p.blockedAccounts[i].Less(hash)
|
// of the blocked account in the blocked accounts list (or the position it should be
|
||||||
})
|
// put at).
|
||||||
if length != 0 && i != length && p.blockedAccounts[i].Equals(hash) {
|
func (p *Policy) isBlockedInternal(dao *dao.Simple, hash util.Uint160) (int, bool) {
|
||||||
return true
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||||
}
|
length := len(cache.blockedAccounts)
|
||||||
return false
|
i := sort.Search(length, func(i int) bool {
|
||||||
|
return !cache.blockedAccounts[i].Less(hash)
|
||||||
|
})
|
||||||
|
if length != 0 && i != length && cache.blockedAccounts[i].Equals(hash) {
|
||||||
|
return i, true
|
||||||
}
|
}
|
||||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
return i, false
|
||||||
return dao.GetStorageItem(p.ID, key) != nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
@ -256,12 +272,8 @@ func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stacki
|
||||||
|
|
||||||
// GetStoragePriceInternal returns current execution fee factor.
|
// GetStoragePriceInternal returns current execution fee factor.
|
||||||
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
|
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
|
||||||
p.lock.RLock()
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
||||||
defer p.lock.RUnlock()
|
return int64(cache.storagePrice)
|
||||||
if p.isValid {
|
|
||||||
return int64(p.storagePrice)
|
|
||||||
}
|
|
||||||
return getIntWithKey(p.ID, d, storagePriceKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
@ -272,10 +284,9 @@ func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) sta
|
||||||
if !p.NEO.checkCommittee(ic) {
|
if !p.NEO.checkCommittee(ic) {
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
|
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
|
||||||
p.isValid = false
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||||
|
cache.storagePrice = value
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,10 +299,9 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
||||||
if !p.NEO.checkCommittee(ic) {
|
if !p.NEO.checkCommittee(ic) {
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
|
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
|
||||||
p.isValid = false
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||||
|
cache.feePerByte = value
|
||||||
return stackitem.Null{}
|
return stackitem.Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,14 +317,19 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
|
||||||
panic("cannot block native contract")
|
panic("cannot block native contract")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.IsBlockedInternal(ic.DAO, hash) {
|
i, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||||
|
if blocked {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{})
|
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{})
|
||||||
p.isValid = false
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||||
|
if len(cache.blockedAccounts) == i {
|
||||||
|
cache.blockedAccounts = append(cache.blockedAccounts, hash)
|
||||||
|
} else {
|
||||||
|
cache.blockedAccounts = append(cache.blockedAccounts[:i+1], cache.blockedAccounts[i:]...)
|
||||||
|
cache.blockedAccounts[i] = hash
|
||||||
|
}
|
||||||
return stackitem.NewBool(true)
|
return stackitem.NewBool(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,14 +340,14 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
||||||
panic("invalid committee signature")
|
panic("invalid committee signature")
|
||||||
}
|
}
|
||||||
hash := toUint160(args[0])
|
hash := toUint160(args[0])
|
||||||
if !p.IsBlockedInternal(ic.DAO, hash) {
|
i, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||||
|
if !blocked {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
ic.DAO.DeleteStorageItem(p.ID, key)
|
ic.DAO.DeleteStorageItem(p.ID, key)
|
||||||
p.isValid = false
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||||
|
cache.blockedAccounts = append(cache.blockedAccounts[:i], cache.blockedAccounts[i+1:]...)
|
||||||
return stackitem.NewBool(true)
|
return stackitem.NewBool(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +356,7 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
||||||
// fee limit.
|
// fee limit.
|
||||||
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
|
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
|
||||||
for _, signer := range tx.Signers {
|
for _, signer := range tx.Signers {
|
||||||
if p.IsBlockedInternal(d, signer.Account) {
|
if _, isBlocked := p.isBlockedInternal(d, signer.Account); isBlocked {
|
||||||
return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
|
return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package stateroot
|
package stateroot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -101,6 +102,34 @@ func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) {
|
||||||
return s.getStateRoot(makeStateRootKey(height))
|
return s.getStateRoot(makeStateRootKey(height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLatestStateHeight returns the latest blockchain height by the given stateroot.
|
||||||
|
func (s *Module) GetLatestStateHeight(root util.Uint256) (uint32, error) {
|
||||||
|
rootBytes := root.BytesBE()
|
||||||
|
rootStartOffset := 1 + 4 // stateroot version (1 byte) + stateroot index (4 bytes)
|
||||||
|
rootEndOffset := rootStartOffset + util.Uint256Size
|
||||||
|
var (
|
||||||
|
h uint32
|
||||||
|
found bool
|
||||||
|
rootKey = makeStateRootKey(s.localHeight.Load())
|
||||||
|
)
|
||||||
|
s.Store.Seek(storage.SeekRange{
|
||||||
|
Prefix: []byte{rootKey[0]}, // DataMPTAux
|
||||||
|
Start: rootKey[1:], // Start is a value that should be appended to the Prefix
|
||||||
|
Backwards: true,
|
||||||
|
}, func(k, v []byte) bool {
|
||||||
|
if len(k) == 5 && bytes.Equal(v[rootStartOffset:rootEndOffset], rootBytes) {
|
||||||
|
h = binary.BigEndian.Uint32(k[1:]) // cut prefix DataMPTAux
|
||||||
|
found = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
return h, storage.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
// CurrentLocalStateRoot returns hash of the local state root.
|
// CurrentLocalStateRoot returns hash of the local state root.
|
||||||
func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
||||||
return s.currentLocal.Load().(util.Uint256)
|
return s.currentLocal.Load().(util.Uint256)
|
||||||
|
|
|
@ -296,3 +296,20 @@ func checkVoteBroadcasted(t *testing.T, bc *core.Blockchain, p *payload.Extensib
|
||||||
require.True(t, len(pubs) > int(valIndex))
|
require.True(t, len(pubs) > int(valIndex))
|
||||||
require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r))
|
require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateroot_GetLatestStateHeight(t *testing.T) {
|
||||||
|
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
||||||
|
c.P2PSigExtensions = true
|
||||||
|
})
|
||||||
|
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||||
|
initBasicChain(t, e)
|
||||||
|
|
||||||
|
m := bc.GetStateModule()
|
||||||
|
for i := uint32(0); i < bc.BlockHeight(); i++ {
|
||||||
|
r, err := m.GetStateRoot(i)
|
||||||
|
require.NoError(t, err)
|
||||||
|
h, err := bc.GetStateModule().GetLatestStateHeight(r.Root)
|
||||||
|
require.NoError(t, err, i)
|
||||||
|
require.Equal(t, i, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -365,7 +365,6 @@ func (s *MemCachedStore) persist(isSync bool) (int, error) {
|
||||||
if !isSync {
|
if !isSync {
|
||||||
s.mut.Unlock()
|
s.mut.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tempstore.ps.PutChangeSet(tempstore.mem, tempstore.stor)
|
err = tempstore.ps.PutChangeSet(tempstore.mem, tempstore.stor)
|
||||||
|
|
||||||
if !isSync {
|
if !isSync {
|
||||||
|
|
|
@ -588,6 +588,33 @@ func (c *Client) InvokeScript(script []byte, signers []transaction.Signer) (*res
|
||||||
return c.invokeSomething("invokescript", p, signers)
|
return c.invokeSomething("invokescript", p, signers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeScriptAtHeight returns the result of the given script after running it
|
||||||
|
// true the VM using the provided chain state retrieved from the specified chain
|
||||||
|
// height.
|
||||||
|
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(height, script)
|
||||||
|
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeScriptAtBlock returns the result of the given script after running it
|
||||||
|
// true the VM using the provided chain state retrieved from the specified block
|
||||||
|
// hash.
|
||||||
|
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(blockHash.StringLE(), script)
|
||||||
|
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeScriptWithState returns the result of the given script after running it
|
||||||
|
// true the VM using the provided chain state retrieved from the specified
|
||||||
|
// stateroot hash.
|
||||||
|
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(stateroot.StringLE(), script)
|
||||||
|
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
// InvokeFunction returns the results after calling the smart contract scripthash
|
// InvokeFunction returns the results after calling the smart contract scripthash
|
||||||
// with the given operation and parameters.
|
// with the given operation and parameters.
|
||||||
// NOTE: this is test invoke and will not affect the blockchain.
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
@ -596,6 +623,33 @@ func (c *Client) InvokeFunction(contract util.Uint160, operation string, params
|
||||||
return c.invokeSomething("invokefunction", p, signers)
|
return c.invokeSomething("invokefunction", p, signers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeFunctionAtHeight returns the results after calling the smart contract
|
||||||
|
// with the given operation and parameters at the given blockchain state
|
||||||
|
// specified by the blockchain height.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(height, contract.StringLE(), operation, params)
|
||||||
|
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeFunctionAtBlock returns the results after calling the smart contract
|
||||||
|
// with the given operation and parameters at given the blockchain state
|
||||||
|
// specified by the block hash.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(blockHash.StringLE(), contract.StringLE(), operation, params)
|
||||||
|
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeFunctionWithState returns the results after calling the smart contract
|
||||||
|
// with the given operation and parameters at the given blockchain state defined
|
||||||
|
// by the specified stateroot hash.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(stateroot.StringLE(), contract.StringLE(), operation, params)
|
||||||
|
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||||
|
}
|
||||||
|
|
||||||
// InvokeContractVerify returns the results after calling `verify` method of the smart contract
|
// InvokeContractVerify returns the results after calling `verify` method of the smart contract
|
||||||
// with the given parameters under verification trigger type.
|
// with the given parameters under verification trigger type.
|
||||||
// NOTE: this is test invoke and will not affect the blockchain.
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
@ -604,6 +658,33 @@ func (c *Client) InvokeContractVerify(contract util.Uint160, params []smartcontr
|
||||||
return c.invokeSomething("invokecontractverify", p, signers, witnesses...)
|
return c.invokeSomething("invokecontractverify", p, signers, witnesses...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeContractVerifyAtHeight returns the results after calling `verify` method
|
||||||
|
// of the smart contract with the given parameters under verification trigger type
|
||||||
|
// at the blockchain state specified by the blockchain height.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(height, contract.StringLE(), params)
|
||||||
|
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeContractVerifyAtBlock returns the results after calling `verify` method
|
||||||
|
// of the smart contract with the given parameters under verification trigger type
|
||||||
|
// at the blockchain state specified by the block hash.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(blockHash.StringLE(), contract.StringLE(), params)
|
||||||
|
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvokeContractVerifyWithState returns the results after calling `verify` method
|
||||||
|
// of the smart contract with the given parameters under verification trigger type
|
||||||
|
// at the blockchain state specified by the stateroot hash.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(stateroot.StringLE(), contract.StringLE(), params)
|
||||||
|
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||||
|
}
|
||||||
|
|
||||||
// invokeSomething is an inner wrapper for Invoke* functions.
|
// invokeSomething is an inner wrapper for Invoke* functions.
|
||||||
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
var resp = new(result.Invoke)
|
var resp = new(result.Invoke)
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"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/core/transaction"
|
||||||
|
@ -766,6 +768,56 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
require.True(t, res.Stack[0].Value().(bool))
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("positive, historic, by height, with signer", func(t *testing.T) {
|
||||||
|
h := chain.BlockHeight() - 1
|
||||||
|
res, err := c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive, historic, by block, with signer", func(t *testing.T) {
|
||||||
|
res, err := c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(int(chain.BlockHeight())-1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive, historic, by stateroot, with signer", func(t *testing.T) {
|
||||||
|
h := chain.BlockHeight() - 1
|
||||||
|
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad, historic, by hash: contract not found", func(t *testing.T) {
|
||||||
|
var h uint32 = 1
|
||||||
|
_, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad, historic, by block: contract not found", func(t *testing.T) {
|
||||||
|
_, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad, historic, by stateroot: contract not found", func(t *testing.T) {
|
||||||
|
var h uint32 = 1
|
||||||
|
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("positive, with signer and witness", func(t *testing.T) {
|
t.Run("positive, with signer and witness", func(t *testing.T) {
|
||||||
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"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/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||||
|
@ -108,46 +109,49 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
||||||
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
||||||
"findstates": (*Server).findStates,
|
"findstates": (*Server).findStates,
|
||||||
"getapplicationlog": (*Server).getApplicationLog,
|
"getapplicationlog": (*Server).getApplicationLog,
|
||||||
"getbestblockhash": (*Server).getBestBlockHash,
|
"getbestblockhash": (*Server).getBestBlockHash,
|
||||||
"getblock": (*Server).getBlock,
|
"getblock": (*Server).getBlock,
|
||||||
"getblockcount": (*Server).getBlockCount,
|
"getblockcount": (*Server).getBlockCount,
|
||||||
"getblockhash": (*Server).getBlockHash,
|
"getblockhash": (*Server).getBlockHash,
|
||||||
"getblockheader": (*Server).getBlockHeader,
|
"getblockheader": (*Server).getBlockHeader,
|
||||||
"getblockheadercount": (*Server).getBlockHeaderCount,
|
"getblockheadercount": (*Server).getBlockHeaderCount,
|
||||||
"getblocksysfee": (*Server).getBlockSysFee,
|
"getblocksysfee": (*Server).getBlockSysFee,
|
||||||
"getcommittee": (*Server).getCommittee,
|
"getcommittee": (*Server).getCommittee,
|
||||||
"getconnectioncount": (*Server).getConnectionCount,
|
"getconnectioncount": (*Server).getConnectionCount,
|
||||||
"getcontractstate": (*Server).getContractState,
|
"getcontractstate": (*Server).getContractState,
|
||||||
"getnativecontracts": (*Server).getNativeContracts,
|
"getnativecontracts": (*Server).getNativeContracts,
|
||||||
"getnep11balances": (*Server).getNEP11Balances,
|
"getnep11balances": (*Server).getNEP11Balances,
|
||||||
"getnep11properties": (*Server).getNEP11Properties,
|
"getnep11properties": (*Server).getNEP11Properties,
|
||||||
"getnep11transfers": (*Server).getNEP11Transfers,
|
"getnep11transfers": (*Server).getNEP11Transfers,
|
||||||
"getnep17balances": (*Server).getNEP17Balances,
|
"getnep17balances": (*Server).getNEP17Balances,
|
||||||
"getnep17transfers": (*Server).getNEP17Transfers,
|
"getnep17transfers": (*Server).getNEP17Transfers,
|
||||||
"getpeers": (*Server).getPeers,
|
"getpeers": (*Server).getPeers,
|
||||||
"getproof": (*Server).getProof,
|
"getproof": (*Server).getProof,
|
||||||
"getrawmempool": (*Server).getRawMempool,
|
"getrawmempool": (*Server).getRawMempool,
|
||||||
"getrawtransaction": (*Server).getrawtransaction,
|
"getrawtransaction": (*Server).getrawtransaction,
|
||||||
"getstate": (*Server).getState,
|
"getstate": (*Server).getState,
|
||||||
"getstateheight": (*Server).getStateHeight,
|
"getstateheight": (*Server).getStateHeight,
|
||||||
"getstateroot": (*Server).getStateRoot,
|
"getstateroot": (*Server).getStateRoot,
|
||||||
"getstorage": (*Server).getStorage,
|
"getstorage": (*Server).getStorage,
|
||||||
"gettransactionheight": (*Server).getTransactionHeight,
|
"gettransactionheight": (*Server).getTransactionHeight,
|
||||||
"getunclaimedgas": (*Server).getUnclaimedGas,
|
"getunclaimedgas": (*Server).getUnclaimedGas,
|
||||||
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
||||||
"getversion": (*Server).getVersion,
|
"getversion": (*Server).getVersion,
|
||||||
"invokefunction": (*Server).invokeFunction,
|
"invokefunction": (*Server).invokeFunction,
|
||||||
"invokescript": (*Server).invokescript,
|
"invokefunctionhistoric": (*Server).invokeFunctionHistoric,
|
||||||
"invokecontractverify": (*Server).invokeContractVerify,
|
"invokescript": (*Server).invokescript,
|
||||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
"invokescripthistoric": (*Server).invokescripthistoric,
|
||||||
"submitblock": (*Server).submitBlock,
|
"invokecontractverify": (*Server).invokeContractVerify,
|
||||||
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
"invokecontractverifyhistoric": (*Server).invokeContractVerifyHistoric,
|
||||||
"submitoracleresponse": (*Server).submitOracleResponse,
|
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||||
"validateaddress": (*Server).validateAddress,
|
"submitblock": (*Server).submitBlock,
|
||||||
"verifyproof": (*Server).verifyProof,
|
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
||||||
|
"submitoracleresponse": (*Server).submitOracleResponse,
|
||||||
|
"validateaddress": (*Server).validateAddress,
|
||||||
|
"verifyproof": (*Server).verifyProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
|
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
|
||||||
|
@ -866,7 +870,7 @@ func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method stri
|
||||||
}
|
}
|
||||||
script := bw.Bytes()
|
script := bw.Bytes()
|
||||||
tx := &transaction.Transaction{Script: script}
|
tx := &transaction.Transaction{Script: script}
|
||||||
b, err := s.getFakeNextBlock()
|
b, err := s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -1571,16 +1575,40 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
|
||||||
|
|
||||||
// invokeFunction implements the `invokeFunction` RPC call.
|
// invokeFunction implements the `invokeFunction` RPC call.
|
||||||
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
tx, verbose, respErr := s.getInvokeFunctionParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, nil, verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call.
|
||||||
|
func (s *Server) invokeFunctionHistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
b, respErr := s.getHistoricParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
if len(reqParams) < 2 {
|
if len(reqParams) < 2 {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
tx, verbose, respErr := s.getInvokeFunctionParams(reqParams[1:])
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
|
||||||
|
if len(reqParams) < 2 {
|
||||||
|
return nil, false, response.ErrInvalidParams
|
||||||
|
}
|
||||||
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||||
if responseErr != nil {
|
if responseErr != nil {
|
||||||
return nil, responseErr
|
return nil, false, responseErr
|
||||||
}
|
}
|
||||||
method, err := reqParams[1].GetString()
|
method, err := reqParams[1].GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
var params *request.Param
|
var params *request.Param
|
||||||
if len(reqParams) > 2 {
|
if len(reqParams) > 2 {
|
||||||
|
@ -1590,7 +1618,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
if len(reqParams) > 3 {
|
if len(reqParams) > 3 {
|
||||||
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
}
|
}
|
||||||
|
@ -1598,7 +1626,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
if len(reqParams) > 4 {
|
if len(reqParams) > 4 {
|
||||||
verbose, err = reqParams[4].GetBoolean()
|
verbose, err = reqParams[4].GetBoolean()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tx.Signers) == 0 {
|
if len(tx.Signers) == 0 {
|
||||||
|
@ -1606,28 +1634,48 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
}
|
}
|
||||||
script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
|
script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
return nil, false, response.NewInternalServerError("can't create invocation script", err)
|
||||||
}
|
}
|
||||||
tx.Script = script
|
tx.Script = script
|
||||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
return tx, verbose, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
if len(reqParams) < 1 {
|
tx, verbose, respErr := s.getInvokeScriptParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, nil, verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokescripthistoric implements the `invokescripthistoric` RPC call.
|
||||||
|
func (s *Server) invokescripthistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
b, respErr := s.getHistoricParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
if len(reqParams) < 2 {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
tx, verbose, respErr := s.getInvokeScriptParams(reqParams[1:])
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
|
||||||
|
}
|
||||||
|
|
||||||
script, err := reqParams[0].GetBytesBase64()
|
func (s *Server) getInvokeScriptParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
|
||||||
|
script, err := reqParams.Value(0).GetBytesBase64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := &transaction.Transaction{}
|
tx := &transaction.Transaction{}
|
||||||
if len(reqParams) > 1 {
|
if len(reqParams) > 1 {
|
||||||
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
tx.Scripts = witnesses
|
tx.Scripts = witnesses
|
||||||
|
@ -1636,33 +1684,57 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
||||||
if len(reqParams) > 2 {
|
if len(reqParams) > 2 {
|
||||||
verbose, err = reqParams[2].GetBoolean()
|
verbose, err = reqParams[2].GetBoolean()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tx.Signers) == 0 {
|
if len(tx.Signers) == 0 {
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||||
}
|
}
|
||||||
tx.Script = script
|
tx.Script = script
|
||||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
return tx, verbose, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||||
func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
scriptHash, tx, invocationScript, respErr := s.getInvokeContractVerifyParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokeContractVerifyHistoric implements the `invokecontractverifyhistoric` RPC call.
|
||||||
|
func (s *Server) invokeContractVerifyHistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
b, respErr := s.getHistoricParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
if len(reqParams) < 2 {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
scriptHash, tx, invocationScript, respErr := s.getInvokeContractVerifyParams(reqParams[1:])
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, b, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getInvokeContractVerifyParams(reqParams request.Params) (util.Uint160, *transaction.Transaction, []byte, *response.Error) {
|
||||||
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||||
if responseErr != nil {
|
if responseErr != nil {
|
||||||
return nil, responseErr
|
return util.Uint160{}, nil, nil, responseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
if len(reqParams) > 1 {
|
if len(reqParams) > 1 {
|
||||||
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
return util.Uint160{}, nil, nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||||
}
|
}
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewRPCError("can't create witness invocation script", err.Error(), err)
|
return util.Uint160{}, nil, nil, response.NewRPCError("can't create witness invocation script", err.Error(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1672,7 +1744,7 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
||||||
if len(reqParams) > 2 {
|
if len(reqParams) > 2 {
|
||||||
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return util.Uint160{}, nil, nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
tx.Scripts = witnesses
|
tx.Scripts = witnesses
|
||||||
|
@ -1680,16 +1752,51 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
||||||
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
||||||
tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}}
|
tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}}
|
||||||
}
|
}
|
||||||
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, false)
|
return scriptHash, tx, invocationScript, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getFakeNextBlock() (*block.Block, error) {
|
// getHistoricParams checks that historic calls are supported and returns fake block
|
||||||
|
// with the specified index to perform the historic call. It also checks that
|
||||||
|
// specified stateroot is stored at the specified height for further request
|
||||||
|
// handling consistency.
|
||||||
|
func (s *Server) getHistoricParams(reqParams request.Params) (*block.Block, *response.Error) {
|
||||||
|
if s.chain.GetConfig().KeepOnlyLatestState {
|
||||||
|
return nil, response.NewInvalidRequestError("only latest state is supported", errKeepOnlyLatestState)
|
||||||
|
}
|
||||||
|
if len(reqParams) < 1 {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
height, respErr := s.blockHeightFromParam(reqParams.Value(0))
|
||||||
|
if respErr != nil {
|
||||||
|
hash, err := reqParams.Value(0).GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInvalidParamsError("invalid block hash or index or stateroot hash", err)
|
||||||
|
}
|
||||||
|
b, err := s.chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
stateH, err := s.chain.GetStateModule().GetLatestStateHeight(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInvalidParamsError(fmt.Sprintf("unknown block or stateroot: %s", err), err)
|
||||||
|
}
|
||||||
|
height = int(stateH)
|
||||||
|
} else {
|
||||||
|
height = int(b.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := s.getFakeNextBlock(uint32(height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError(fmt.Sprintf("can't create fake block for height %d: %s", height, err.Error()), err)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) {
|
||||||
// When transferring funds, script execution does no auto GAS claim,
|
// When transferring funds, script execution does no auto GAS claim,
|
||||||
// because it depends on persisting tx height.
|
// because it depends on persisting tx height.
|
||||||
// This is why we provide block here.
|
// This is why we provide block here.
|
||||||
b := block.New(s.stateRootEnabled)
|
b := block.New(s.stateRootEnabled)
|
||||||
b.Index = s.chain.BlockHeight() + 1
|
b.Index = nextBlockHeight
|
||||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight())))
|
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(nextBlockHeight - 1)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1702,12 +1809,23 @@ func (s *Server) getFakeNextBlock() (*block.Block, error) {
|
||||||
// witness invocation script in case of `verification` trigger (it pushes `verify`
|
// witness invocation script in case of `verification` trigger (it pushes `verify`
|
||||||
// arguments on stack before verification). In case of contract verification
|
// arguments on stack before verification). In case of contract verification
|
||||||
// contractScriptHash should be specified.
|
// contractScriptHash should be specified.
|
||||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, verbose bool) (*result.Invoke, *response.Error) {
|
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*result.Invoke, *response.Error) {
|
||||||
b, err := s.getFakeNextBlock()
|
var (
|
||||||
if err != nil {
|
err error
|
||||||
return nil, response.NewInternalServerError("can't create fake block", err)
|
ic *interop.Context
|
||||||
|
)
|
||||||
|
if b == nil {
|
||||||
|
b, err = s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError("can't create fake block", err)
|
||||||
|
}
|
||||||
|
ic = s.chain.GetTestVM(t, tx, b)
|
||||||
|
} else {
|
||||||
|
ic, err = s.chain.GetTestHistoricVM(t, tx, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError("failed to create historic VM", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ic := s.chain.GetTestVM(t, tx, b)
|
|
||||||
if verbose {
|
if verbose {
|
||||||
ic.VM.EnableInvocationTree()
|
ic.VM.EnableInvocationTree()
|
||||||
}
|
}
|
||||||
|
@ -1720,9 +1838,9 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
|
||||||
ic.VM.GasLimit = gasPolicy
|
ic.VM.GasLimit = gasPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
|
err = s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("can't prepare verification VM", err)
|
return nil, response.NewInternalServerError(fmt.Sprintf("can't prepare verification VM: %s", err.Error()), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ic.VM.LoadScriptWithFlags(script, callflag.All)
|
ic.VM.LoadScriptWithFlags(script, callflag.All)
|
||||||
|
|
|
@ -75,6 +75,7 @@ const (
|
||||||
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
||||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||||
|
block20StateRootLE = "19ec3c3d01afe5274e8bb4a393c97da708c5608c5b0ad116c16108b6a04fb08e"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -999,6 +1000,134 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokefunctionhistoric": {
|
||||||
|
{
|
||||||
|
name: "positive, by index",
|
||||||
|
params: `[20, "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotNil(t, res.Script)
|
||||||
|
assert.NotEqual(t, "", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, by stateroot",
|
||||||
|
params: `["` + block20StateRootLE + `", "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotNil(t, res.Script)
|
||||||
|
assert.NotEqual(t, "", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, with notifications",
|
||||||
|
params: `[20, "` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
|
||||||
|
result: func(e *executor) interface{} {
|
||||||
|
script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
|
||||||
|
return &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
GasConsumed: 32167260,
|
||||||
|
Script: script,
|
||||||
|
Stack: []stackitem.Item{stackitem.Make(true)},
|
||||||
|
Notifications: []state.NotificationEvent{{
|
||||||
|
ScriptHash: nnsHash,
|
||||||
|
Name: "Transfer",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.Make([]byte{0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x08, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}),
|
||||||
|
stackitem.Make([]byte{0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b}),
|
||||||
|
stackitem.Make(1),
|
||||||
|
stackitem.Make("neo.com"),
|
||||||
|
}),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, verbose",
|
||||||
|
params: `[20, "` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`,
|
||||||
|
result: func(e *executor) interface{} {
|
||||||
|
script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
|
||||||
|
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib)
|
||||||
|
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||||
|
return &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
GasConsumed: 15928320,
|
||||||
|
Script: script,
|
||||||
|
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||||
|
Notifications: []state.NotificationEvent{},
|
||||||
|
Diagnostics: &result.InvokeDiag{
|
||||||
|
Changes: []storage.Operation{},
|
||||||
|
Invocations: []*vm.InvocationTree{{
|
||||||
|
Current: hash.Hash160(script),
|
||||||
|
Calls: []*vm.InvocationTree{
|
||||||
|
{
|
||||||
|
Current: nnsHash,
|
||||||
|
Calls: []*vm.InvocationTree{
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: cryptoHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: cryptoHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: cryptoHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
params: `[20]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[20, 42, "test", []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a scripthash",
|
||||||
|
params: `[20,"qwerty", "test", []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad params",
|
||||||
|
params: `[20,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad height",
|
||||||
|
params: `[100500,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad stateroot",
|
||||||
|
params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"invokescript": {
|
"invokescript": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -1098,6 +1227,132 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokescripthistoric": {
|
||||||
|
{
|
||||||
|
name: "positive, by index",
|
||||||
|
params: `[20,"UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotEqual(t, "", res.Script)
|
||||||
|
assert.NotEqual(t, "", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, by stateroot",
|
||||||
|
params: `["` + block20StateRootLE + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotEqual(t, "", res.Script)
|
||||||
|
assert.NotEqual(t, "", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive,verbose",
|
||||||
|
params: `[20, "UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY=",[],true]`,
|
||||||
|
result: func(e *executor) interface{} {
|
||||||
|
script := []byte{0x51, 0xc5, 0x6b, 0xd, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x68, 0xf, 0x4e, 0x65, 0x6f, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4c, 0x6f, 0x67, 0x61, 0x6c, 0x75, 0x66}
|
||||||
|
return &result.Invoke{
|
||||||
|
State: "FAULT",
|
||||||
|
GasConsumed: 60,
|
||||||
|
Script: script,
|
||||||
|
Stack: []stackitem.Item{},
|
||||||
|
FaultException: "at instruction 0 (ROT): too big index",
|
||||||
|
Notifications: []state.NotificationEvent{},
|
||||||
|
Diagnostics: &result.InvokeDiag{
|
||||||
|
Changes: []storage.Operation{},
|
||||||
|
Invocations: []*vm.InvocationTree{{
|
||||||
|
Current: hash.Hash160(script),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, good witness",
|
||||||
|
// script is base64-encoded `invokescript_contract.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix
|
||||||
|
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`, invokescriptContractAVM),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, big.NewInt(3), res.Stack[0].Value())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, bad witness of second hash",
|
||||||
|
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`, invokescriptContractAVM),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, big.NewInt(2), res.Stack[0].Value())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, no good hashes",
|
||||||
|
params: fmt.Sprintf(`[20,"%s"]`, invokescriptContractAVM),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, big.NewInt(1), res.Stack[0].Value())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, bad hashes witness",
|
||||||
|
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`, invokescriptContractAVM),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.Equal(t, 1, len(res.Stack))
|
||||||
|
assert.Equal(t, big.NewInt(1), res.Stack[0].Value())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no script",
|
||||||
|
params: `[20]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[20,42]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bas string",
|
||||||
|
params: `[20, "qwerty"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bas height",
|
||||||
|
params: `[100500,"qwerty"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bas stateroot",
|
||||||
|
params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"invokecontractverify": {
|
"invokecontractverify": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -1203,6 +1458,129 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokecontractverifyhistoric": {
|
||||||
|
{
|
||||||
|
name: "positive, by index",
|
||||||
|
params: fmt.Sprintf(`[20,"%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address()))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, by stateroot",
|
||||||
|
params: fmt.Sprintf(`["`+block20StateRootLE+`","%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address()))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, no signers",
|
||||||
|
params: fmt.Sprintf(`[20,"%s", []]`, verifyContractHash),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Nil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, no arguments",
|
||||||
|
params: fmt.Sprintf(`[20,"%s"]`, verifyContractHash),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Nil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, with signers and scripts",
|
||||||
|
params: fmt.Sprintf(`[20,"%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Nil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, with arguments, result=true",
|
||||||
|
params: fmt.Sprintf(`[20,"%s", [{"type": "String", "value": "good_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": false}]]`, verifyWithArgsContractHash),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
expectedInvScript := io.NewBufBinWriter()
|
||||||
|
emit.Int(expectedInvScript.BinWriter, 0)
|
||||||
|
emit.Int(expectedInvScript.BinWriter, int64(4))
|
||||||
|
emit.String(expectedInvScript.BinWriter, "good_string")
|
||||||
|
require.NoError(t, expectedInvScript.Err)
|
||||||
|
assert.Equal(t, expectedInvScript.Bytes(), res.Script) // witness invocation script (pushes args of `verify` on stack)
|
||||||
|
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, with arguments, result=false",
|
||||||
|
params: fmt.Sprintf(`[20, "%s", [{"type": "String", "value": "invalid_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": false}]]`, verifyWithArgsContractHash),
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
expectedInvScript := io.NewBufBinWriter()
|
||||||
|
emit.Int(expectedInvScript.BinWriter, 0)
|
||||||
|
emit.Int(expectedInvScript.BinWriter, int64(4))
|
||||||
|
emit.String(expectedInvScript.BinWriter, "invalid_string")
|
||||||
|
require.NoError(t, expectedInvScript.Err)
|
||||||
|
assert.Equal(t, expectedInvScript.Bytes(), res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown contract",
|
||||||
|
params: fmt.Sprintf(`[20, "%s", []]`, util.Uint160{}.String()),
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no args",
|
||||||
|
params: `[20]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[20,42, []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"sendrawtransaction": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
Loading…
Reference in a new issue