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
|
||||
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
|
||||
|
||||
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()
|
||||
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) {
|
||||
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
|
||||
|
|
|
@ -76,7 +76,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
|
||||
func initBasicChain(t *testing.T, e *neotest.Executor) {
|
||||
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
|
||||
|
|
|
@ -433,14 +433,9 @@ func (bc *Blockchain) init() error {
|
|||
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 {
|
||||
return fmt.Errorf("can't init cache for NEO native contract: %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)
|
||||
return fmt.Errorf("can't init natives cache: %w", err)
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
|
||||
err = bc.contracts.NEO.InitializeCache(bc, bc.dao)
|
||||
err = bc.initializeNativeCache(block.Index, bc.dao)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
// critical for correct Blockchain operation.
|
||||
func (bc *Blockchain) Run() {
|
||||
|
@ -1220,14 +1237,14 @@ func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress()}
|
||||
nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal()
|
||||
newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress(bc.dao)}
|
||||
nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao)
|
||||
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
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.
|
||||
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.
|
||||
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
|
||||
|
@ -1823,7 +1840,7 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra
|
|||
curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1)
|
||||
if oldVC == nil || oldVC != curVC {
|
||||
m := smartcontract.GetDefaultHonestNodeCount(curVC)
|
||||
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal())
|
||||
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao))
|
||||
defaultWitness = transaction.Witness{
|
||||
InvocationScript: make([]byte, 66*m),
|
||||
VerificationScript: verification,
|
||||
|
@ -1939,7 +1956,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||
if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
|
||||
return err
|
||||
}
|
||||
err = pool.Add(t, feer, data...)
|
||||
|
@ -1963,11 +1980,11 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
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 {
|
||||
switch attrType := tx.Attributes[i].Type; attrType {
|
||||
case transaction.HighPriority:
|
||||
h := bc.contracts.NEO.GetCommitteeAddress()
|
||||
h := bc.contracts.NEO.GetCommitteeAddress(d)
|
||||
if !tx.HasSigner(h) {
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||
if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
|
||||
return false
|
||||
}
|
||||
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.
|
||||
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
||||
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
||||
pubs := bc.contracts.NEO.GetCommitteeMembers(bc.dao)
|
||||
sort.Sort(pubs)
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
// GetValidators returns current validators.
|
||||
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.
|
||||
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.
|
||||
|
@ -2148,6 +2165,41 @@ func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *
|
|||
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.
|
||||
var (
|
||||
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/pkg/config"
|
||||
"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/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -329,7 +328,7 @@ func TestBlockchain_BaseExecFeeBaseStoragePrice_Compat(t *testing.T) {
|
|||
bc := newTestChain(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.GetStoragePrice(), ic.BaseStorageFee())
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ type Blockchainer interface {
|
|||
GetStateModule() StateRoot
|
||||
GetStorageItem(id int32, key []byte) state.StorageItem
|
||||
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)
|
||||
SetOracle(service services.Oracle)
|
||||
mempool.Feer // fee interface
|
||||
|
|
|
@ -15,4 +15,5 @@ type StateRoot interface {
|
|||
GetState(root util.Uint256, key []byte) ([]byte, error)
|
||||
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
||||
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
||||
GetLatestStateHeight(root util.Uint256) (uint32, error)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
iocore "io"
|
||||
"sync"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -34,11 +35,28 @@ var (
|
|||
type Simple struct {
|
||||
Version Version
|
||||
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
|
||||
keyBuf []byte
|
||||
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.
|
||||
func NewSimple(backend storage.Store, stateRootInHeader bool, p2pSigExtensions bool) *Simple {
|
||||
st := storage.NewMemCachedStore(backend)
|
||||
|
@ -52,7 +70,8 @@ func newSimple(st *storage.MemCachedStore, stateRootInHeader bool, p2pSigExtensi
|
|||
StateRootInHeader: stateRootInHeader,
|
||||
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 {
|
||||
d := NewSimple(dao.Store, dao.Version.StateRootInHeader, dao.Version.P2PSigExtensions)
|
||||
d.Version = dao.Version
|
||||
d.nativeCachePS = dao
|
||||
return d
|
||||
}
|
||||
|
||||
// GetPrivate returns new DAO instance with another layer of private
|
||||
// MemCachedStore around the current DAO Store.
|
||||
func (dao *Simple) GetPrivate() *Simple {
|
||||
d := &Simple{}
|
||||
*d = *dao // Inherit everything...
|
||||
d := &Simple{
|
||||
Version: dao.Version,
|
||||
keyBuf: dao.keyBuf,
|
||||
dataBuf: dao.dataBuf,
|
||||
} // Inherit everything...
|
||||
d.Store = storage.NewPrivateMemCachedStore(dao.Store) // except storage, wrap another layer.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -809,6 +839,17 @@ func (dao *Simple) getDataBuf() *io.BufBinWriter {
|
|||
// Persist flushes all the changes made into the (supposedly) persistent
|
||||
// underlying store. It doesn't block accesses to DAO from other threads.
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -816,5 +857,77 @@ func (dao *Simple) Persist() (int, error) {
|
|||
// underlying store. It's a synchronous version of Persist that doesn't allow
|
||||
// other threads to work with DAO while flushing the Store.
|
||||
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()
|
||||
}
|
||||
|
||||
// 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/interop/interopnames"
|
||||
"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/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -339,3 +340,31 @@ func (ic *Context) Exec() error {
|
|||
defer ic.Finalize()
|
||||
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 {
|
||||
IsBlockedInternal(*dao.Simple, util.Uint160) bool
|
||||
IsBlocked(*dao.Simple, util.Uint160) bool
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if nc.Metadata().Name == nativenames.Policy {
|
||||
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())
|
||||
}
|
||||
break
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"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/dao"
|
||||
"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/iterator"
|
||||
|
@ -530,10 +529,10 @@ func TestStorageFind(t *testing.T) {
|
|||
|
||||
// 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)
|
||||
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()
|
||||
return v, context, chain
|
||||
}
|
||||
|
@ -552,10 +551,7 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C
|
|||
},
|
||||
}
|
||||
|
||||
chain := newTestChain(t)
|
||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
|
||||
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
||||
v := context.SpawnVM()
|
||||
v, context, chain := createVM(t)
|
||||
return v, contractState, context, chain
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,7 @@ import (
|
|||
"runtime"
|
||||
"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/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"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.Estack().PushVal(value)
|
||||
chain := newTestChain(t)
|
||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
|
||||
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
||||
context := chain.newInteropContext(trigger.Application, chain.dao, nil, nil)
|
||||
context.VM = v
|
||||
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
|
||||
// "unhashed" counterparts until the stop condition is satisfied.
|
||||
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) {
|
||||
return err
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ func (b *Billet) Traverse(process func(pathToNode []byte, node Node, nodeBytes [
|
|||
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 {
|
||||
// We're not interested in EmptyNodes, and they do not affect the
|
||||
// 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 b.traverse(r, path, from, process, ignoreStorageErr)
|
||||
return b.traverse(r, path, from, process, ignoreStorageErr, backwards)
|
||||
}
|
||||
if len(from) == 0 {
|
||||
bytes := slice.Copy(curr.Bytes())
|
||||
|
@ -242,22 +242,36 @@ func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode
|
|||
var (
|
||||
startIndex byte
|
||||
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 {
|
||||
endIndex = lastChild
|
||||
if backwards {
|
||||
endIndex = 0
|
||||
}
|
||||
startIndex, from = splitPath(from)
|
||||
}
|
||||
for i := startIndex; i < endIndex; i++ {
|
||||
for i := int(startIndex); cmp(i); i += step {
|
||||
var newPath []byte
|
||||
if i == lastChild {
|
||||
newPath = path
|
||||
} else {
|
||||
newPath = append(path, i)
|
||||
newPath = append(path, byte(i))
|
||||
}
|
||||
if i != startIndex {
|
||||
if byte(i) != startIndex {
|
||||
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 !errors.Is(err, errStop) {
|
||||
return nil, err
|
||||
|
@ -276,7 +290,7 @@ func (b *Billet) traverse(curr Node, path, from []byte, process func(pathToNode
|
|||
} else {
|
||||
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) {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -625,7 +625,7 @@ func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
|
|||
}
|
||||
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) {
|
||||
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)
|
||||
|
||||
gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions)
|
||||
neo := newNEO()
|
||||
neo := newNEO(cfg)
|
||||
policy := newPolicy()
|
||||
neo.GAS = gas
|
||||
neo.Policy = policy
|
||||
|
|
|
@ -3,6 +3,7 @@ package native
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
@ -31,12 +32,6 @@ type Designate struct {
|
|||
interop.ContractMD
|
||||
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 bool
|
||||
|
||||
|
@ -53,6 +48,16 @@ type roleData struct {
|
|||
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 (
|
||||
designateContractID = -8
|
||||
|
||||
|
@ -73,6 +78,22 @@ var (
|
|||
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 {
|
||||
return r == noderoles.Oracle || r == noderoles.StateValidator ||
|
||||
r == noderoles.NeoFSAlphabet || (s.p2pSigExtensionsEnabled && r == noderoles.P2PNotary)
|
||||
|
@ -102,8 +123,30 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -114,26 +157,19 @@ func (s *Designate) OnPersist(ic *interop.Context) error {
|
|||
|
||||
// PostPersist implements Contract interface.
|
||||
func (s *Designate) PostPersist(ic *interop.Context) error {
|
||||
if !s.rolesChanged() {
|
||||
cache := ic.DAO.GetRWCache(s.ID).(*DesignationCache)
|
||||
if !cache.rolesChangedFlag {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.updateCachedRoleData(&s.oracles, ic.DAO, noderoles.Oracle); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
s.notifyRoleChanged(&cache.oracles, noderoles.Oracle)
|
||||
s.notifyRoleChanged(&cache.stateVals, noderoles.StateValidator)
|
||||
s.notifyRoleChanged(&cache.neofsAlphabet, noderoles.NeoFSAlphabet)
|
||||
if s.p2pSigExtensionsEnabled {
|
||||
if err := s.updateCachedRoleData(&s.notaries, ic.DAO, noderoles.P2PNotary); err != nil {
|
||||
return err
|
||||
}
|
||||
s.notifyRoleChanged(&cache.notaries, noderoles.P2PNotary)
|
||||
}
|
||||
|
||||
s.rolesChangedFlag.Store(false)
|
||||
cache.rolesChangedFlag = false
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -152,7 +188,7 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It
|
|||
panic(ErrInvalidIndex)
|
||||
}
|
||||
index := ind.Uint64()
|
||||
if index > uint64(ic.Chain.BlockHeight()+1) {
|
||||
if index > uint64(ic.BlockHeight()+1) {
|
||||
panic(ErrInvalidIndex)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(nodes) == 0 {
|
||||
return util.Uint160{}
|
||||
|
@ -181,47 +212,58 @@ func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.
|
|||
return hash.Hash160(script)
|
||||
}
|
||||
|
||||
func (s *Designate) updateCachedRoleData(v *atomic.Value, d *dao.Simple, r noderoles.Role) error {
|
||||
nodeKeys, height, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
||||
// updateCachedRoleData fetches the most recent role data from the storage and
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
v.Store(&roleData{
|
||||
nodes: nodeKeys,
|
||||
addr: s.hashFromNodes(r, nodeKeys),
|
||||
height: height,
|
||||
})
|
||||
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())
|
||||
}
|
||||
}
|
||||
v.nodes = nodeKeys
|
||||
v.addr = s.hashFromNodes(r, nodeKeys)
|
||||
v.height = height
|
||||
cache.rolesChangedFlag = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Designate) getCachedRoleData(r noderoles.Role) *roleData {
|
||||
var val interface{}
|
||||
func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) {
|
||||
switch r {
|
||||
case noderoles.Oracle:
|
||||
val = s.oracles.Load()
|
||||
case noderoles.StateValidator:
|
||||
val = s.stateVals.Load()
|
||||
case noderoles.NeoFSAlphabet:
|
||||
val = s.neofsAlphabet.Load()
|
||||
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
||||
orc.UpdateOracleNodes(v.nodes.Copy())
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -231,17 +273,11 @@ func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util
|
|||
if !s.isValidRole(r) {
|
||||
return util.Uint160{}, ErrInvalidRole
|
||||
}
|
||||
if !s.rolesChanged() {
|
||||
if val := s.getCachedRoleData(r); val != nil {
|
||||
return val.addr, nil
|
||||
}
|
||||
cache := d.GetROCache(s.ID).(*DesignationCache)
|
||||
if val := getCachedRoleData(cache, r); val != nil {
|
||||
return val.addr, nil
|
||||
}
|
||||
nodes, _, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
// We only have hashing defined for oracles now.
|
||||
return s.hashFromNodes(r, nodes), nil
|
||||
return util.Uint160{}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return nil, 0, ErrInvalidRole
|
||||
}
|
||||
if !s.rolesChanged() {
|
||||
if val := s.getCachedRoleData(r); val != nil && val.height <= index {
|
||||
cache := d.GetROCache(s.ID).(*DesignationCache)
|
||||
if val := getCachedRoleData(cache, r); val != nil {
|
||||
if val.height <= index {
|
||||
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 (
|
||||
ns NodeList
|
||||
bestIndex uint32
|
||||
|
@ -310,7 +357,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
|||
if !s.isValidRole(r) {
|
||||
return ErrInvalidRole
|
||||
}
|
||||
h := s.NEO.GetCommitteeAddress()
|
||||
h := s.NEO.GetCommitteeAddress(ic.DAO)
|
||||
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
|
||||
return ErrInvalidWitness
|
||||
}
|
||||
|
@ -327,12 +374,18 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
|||
}
|
||||
sort.Sort(pubs)
|
||||
nl := NodeList(pubs)
|
||||
s.rolesChangedFlag.Store(true)
|
||||
|
||||
err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
|
||||
if err != nil {
|
||||
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{
|
||||
ScriptHash: s.Hash,
|
||||
Name: DesignationEventName,
|
||||
|
@ -355,8 +408,3 @@ func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {
|
|||
u := bi.Uint64()
|
||||
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 {
|
||||
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])
|
||||
}
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||
hash := getBlockHashFromItem(ic.Chain, params[0])
|
||||
block, err := ic.Chain.GetBlock(hash)
|
||||
if err != nil || !isTraceableBlock(ic.Chain, block.Index) {
|
||||
hash := getBlockHashFromItem(ic, params[0])
|
||||
block, err := ic.GetBlock(hash)
|
||||
if err != nil || !isTraceableBlock(ic, block.Index) {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return BlockToStackItem(block)
|
||||
|
@ -124,7 +124,7 @@ func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackite
|
|||
// getTransaction returns transaction to the SC.
|
||||
func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||
tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
||||
if err != nil || !isTraceableBlock(ic, h) {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
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.
|
||||
func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||
_, h, err := getTransactionAndHeight(ic.DAO, params[0])
|
||||
if err != nil || !isTraceableBlock(ic.Chain, h) {
|
||||
if err != nil || !isTraceableBlock(ic, h) {
|
||||
return stackitem.Make(-1)
|
||||
}
|
||||
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
|
||||
// block with height or hash specified.
|
||||
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])
|
||||
block, err := ic.Chain.GetBlock(hash)
|
||||
if err != nil || !isTraceableBlock(ic.Chain, block.Index) {
|
||||
block, err := ic.GetBlock(hash)
|
||||
if err != nil || !isTraceableBlock(ic, block.Index) {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
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.
|
||||
func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item {
|
||||
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 SignersToStackItem(tx.Signers)
|
||||
|
@ -170,7 +170,7 @@ func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.I
|
|||
panic(err)
|
||||
}
|
||||
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(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
|
||||
// the block with index specified.
|
||||
func isTraceableBlock(bc interop.Ledger, index uint32) bool {
|
||||
height := bc.BlockHeight()
|
||||
MaxTraceableBlocks := bc.GetConfig().MaxTraceableBlocks
|
||||
func isTraceableBlock(ic *interop.Context, index uint32) bool {
|
||||
height := ic.BlockHeight()
|
||||
MaxTraceableBlocks := ic.Chain.GetConfig().MaxTraceableBlocks
|
||||
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
|
||||
// block hashes as parameters, thus this function is needed. It's supposed to
|
||||
// be called within VM context, so it panics if anything goes wrong.
|
||||
func getBlockHashFromItem(bc interop.Ledger, item stackitem.Item) util.Uint256 {
|
||||
func getBlockHashFromItem(ic *interop.Context, item stackitem.Item) util.Uint256 {
|
||||
bigindex, err := item.TryInteger()
|
||||
if err == nil && bigindex.IsUint64() {
|
||||
index := bigindex.Uint64()
|
||||
if index > math.MaxUint32 {
|
||||
panic("bad block index")
|
||||
}
|
||||
if uint32(index) > bc.BlockHeight() {
|
||||
if uint32(index) > ic.BlockHeight() {
|
||||
panic(fmt.Errorf("no block with index %d", index))
|
||||
}
|
||||
return bc.GetHeaderHash(int(index))
|
||||
return ic.Chain.GetHeaderHash(int(index))
|
||||
}
|
||||
hash, err := getUint256FromItem(item)
|
||||
if err != nil {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
|
@ -30,8 +29,9 @@ import (
|
|||
type Management struct {
|
||||
interop.ContractMD
|
||||
NEO *NEO
|
||||
}
|
||||
|
||||
mtx sync.RWMutex
|
||||
type ManagementCache struct {
|
||||
contracts map[util.Uint160]*state.Contract
|
||||
// nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist.
|
||||
nep11 map[util.Uint160]struct{}
|
||||
|
@ -57,6 +57,33 @@ var (
|
|||
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.
|
||||
func MakeContractKey(h util.Uint160) []byte {
|
||||
return makeUint160Key(prefixContract, h)
|
||||
|
@ -66,9 +93,6 @@ func MakeContractKey(h util.Uint160) []byte {
|
|||
func newManagement() *Management {
|
||||
var m = &Management{
|
||||
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()
|
||||
|
||||
|
@ -146,25 +170,12 @@ func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) sta
|
|||
|
||||
// GetContract returns contract with given hash from given DAO.
|
||||
func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
|
||||
m.mtx.RLock()
|
||||
cs, ok := m.contracts[hash]
|
||||
m.mtx.RUnlock()
|
||||
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||
cs, ok := cache.contracts[hash]
|
||||
if !ok {
|
||||
return nil, storage.ErrKeyNotFound
|
||||
} else if cs != nil {
|
||||
return cs, nil
|
||||
}
|
||||
return m.getContractFromDAO(d, hash)
|
||||
}
|
||||
|
||||
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
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (m *Management) markUpdated(h util.Uint160) {
|
||||
m.mtx.Lock()
|
||||
// Just set it to nil, to refresh cache in `PostPersist`.
|
||||
m.contracts[h] = nil
|
||||
m.mtx.Unlock()
|
||||
func (m *Management) markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Contract) {
|
||||
cache := d.GetRWCache(m.ID).(*ManagementCache)
|
||||
delete(cache.nep11, hash)
|
||||
delete(cache.nep17, hash)
|
||||
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.
|
||||
// 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) {
|
||||
h := state.CreateContractHash(sender, neff.Checksum, manif.Name)
|
||||
key := MakeContractKey(h)
|
||||
si := d.GetStorageItem(m.ID, key)
|
||||
if si != nil {
|
||||
_, err := m.GetContract(d, h)
|
||||
if err == nil {
|
||||
return nil, errors.New("contract already exists")
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
m.markUpdated(newcontract.Hash)
|
||||
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.
|
||||
// if NEF was provided, update the contract script
|
||||
if neff != nil {
|
||||
m.markUpdated(hash)
|
||||
contract.NEF = *neff
|
||||
}
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("invalid manifest: %w", err)
|
||||
}
|
||||
m.markUpdated(hash)
|
||||
contract.Manifest = *manif
|
||||
}
|
||||
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)
|
||||
return true
|
||||
})
|
||||
m.markUpdated(hash)
|
||||
m.markUpdated(d, hash, 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
|
||||
// an internal method that must be called with m.mtx lock taken.
|
||||
func (m *Management) updateContractCache(cs *state.Contract) {
|
||||
m.contracts[cs.Hash] = cs
|
||||
func updateContractCache(cache *ManagementCache, cs *state.Contract) {
|
||||
cache.contracts[cs.Hash] = cs
|
||||
if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) {
|
||||
m.nep11[cs.Hash] = struct{}{}
|
||||
cache.nep11[cs.Hash] = struct{}{}
|
||||
}
|
||||
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
||||
m.nep17[cs.Hash] = struct{}{}
|
||||
cache.nep17[cs.Hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// OnPersist implements Contract interface.
|
||||
func (m *Management) OnPersist(ic *interop.Context) error {
|
||||
var cache *ManagementCache
|
||||
for _, native := range ic.Natives {
|
||||
md := native.Metadata()
|
||||
history := md.UpdateHistory
|
||||
|
@ -466,16 +478,17 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
|||
cs := &state.Contract{
|
||||
ContractBase: md.ContractBase,
|
||||
}
|
||||
err := m.PutContractState(ic.DAO, cs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := native.Initialize(ic); err != nil {
|
||||
return fmt.Errorf("initializing %s native contract: %w", md.Name, err)
|
||||
}
|
||||
m.mtx.Lock()
|
||||
m.updateContractCache(cs)
|
||||
m.mtx.Unlock()
|
||||
err := m.putContractState(ic.DAO, cs, false) // Perform cache update manually.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cache == nil {
|
||||
cache = ic.DAO.GetRWCache(m.ID).(*ManagementCache)
|
||||
}
|
||||
updateContractCache(cache, cs)
|
||||
}
|
||||
|
||||
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
|
||||
// called only when deploying native contracts.
|
||||
func (m *Management) InitializeCache(d *dao.Simple) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
cache := &ManagementCache{
|
||||
contracts: make(map[util.Uint160]*state.Contract),
|
||||
nep11: make(map[util.Uint160]struct{}),
|
||||
nep17: make(map[util.Uint160]struct{}),
|
||||
}
|
||||
|
||||
var initErr error
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
m.updateContractCache(cs)
|
||||
updateContractCache(cache, cs)
|
||||
return true
|
||||
})
|
||||
return initErr
|
||||
if initErr != nil {
|
||||
return initErr
|
||||
}
|
||||
d.SetCache(m.ID, cache)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostPersist implements Contract interface.
|
||||
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
|
||||
}
|
||||
|
||||
// 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 returned.
|
||||
func (m *Management) GetNEP11Contracts() []util.Uint160 {
|
||||
m.mtx.RLock()
|
||||
result := make([]util.Uint160, 0, len(m.nep11))
|
||||
for h := range m.nep11 {
|
||||
func (m *Management) GetNEP11Contracts(d *dao.Simple) []util.Uint160 {
|
||||
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||
result := make([]util.Uint160, 0, len(cache.nep11))
|
||||
for h := range cache.nep11 {
|
||||
result = append(result, h)
|
||||
}
|
||||
m.mtx.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// 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 returned.
|
||||
func (m *Management) GetNEP17Contracts() []util.Uint160 {
|
||||
m.mtx.RLock()
|
||||
result := make([]util.Uint160, 0, len(m.nep17))
|
||||
for h := range m.nep17 {
|
||||
func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 {
|
||||
cache := d.GetROCache(m.ID).(*ManagementCache)
|
||||
result := make([]util.Uint160, 0, len(cache.nep17))
|
||||
for h := range cache.nep17 {
|
||||
result = append(result, h)
|
||||
}
|
||||
m.mtx.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -552,16 +554,30 @@ func (m *Management) GetNEP17Contracts() []util.Uint160 {
|
|||
func (m *Management) Initialize(ic *interop.Context) error {
|
||||
setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee)
|
||||
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
|
||||
}
|
||||
|
||||
// PutContractState saves given contract state into given DAO.
|
||||
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)
|
||||
if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
|
||||
return err
|
||||
}
|
||||
m.markUpdated(cs.Hash)
|
||||
if updateCache {
|
||||
m.markUpdated(d, cs.Hash, cs)
|
||||
}
|
||||
if cs.UpdateCounter != 0 { // Update.
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -89,8 +89,11 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
|||
d := dao.NewSimple(storage.NewMemoryStore(), false, false)
|
||||
err := mgmt.Initialize(&interop.Context{DAO: d})
|
||||
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
|
||||
script := []byte{byte(opcode.RET)}
|
||||
|
@ -104,29 +107,46 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
|||
Parameters: []manifest.Parameter{},
|
||||
})
|
||||
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)
|
||||
|
||||
// PostPersist is not yet called, thus no NEP-17 contracts are expected
|
||||
require.Empty(t, mgmt.GetNEP17Contracts())
|
||||
// c1 contract hash should be returned, as private DAO already contains changed cache.
|
||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(private))
|
||||
|
||||
// Call PostPersist, check c1 contract hash is returned
|
||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
||||
// Lower DAO still shouldn't contain c1, as no Persist was called.
|
||||
require.Empty(t, mgmt.GetNEP17Contracts(d))
|
||||
|
||||
// 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
|
||||
private = d.GetPrivate()
|
||||
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
|
||||
Name: "dummy2",
|
||||
ReturnType: smartcontract.VoidType,
|
||||
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.Equal(t, c1.Hash, c1Updated.Hash)
|
||||
|
||||
// No changes expected before PostPersist call.
|
||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
||||
// No changes expected in lower store.
|
||||
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
|
||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
||||
require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts())
|
||||
// Call Persist, check c1Updated state is returned from lower.
|
||||
_, err = private.Persist()
|
||||
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)
|
||||
g.burn(ic, tx.Sender(), absAmount)
|
||||
}
|
||||
validators := g.NEO.GetNextBlockValidatorsInternal()
|
||||
validators := g.NEO.GetNextBlockValidatorsInternal(ic.DAO)
|
||||
primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
|
||||
var netFee int64
|
||||
for _, tx := range ic.Block.Transactions {
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
|
@ -35,33 +34,33 @@ type NEO struct {
|
|||
GAS *GAS
|
||||
Policy *Policy
|
||||
|
||||
// gasPerBlock represents current value of generated gas per block.
|
||||
// It is append-only and doesn't need to be copied when used.
|
||||
gasPerBlock atomic.Value
|
||||
gasPerBlockChanged atomic.Value
|
||||
// Configuration and standby keys are set in constructor and then
|
||||
// only read from.
|
||||
cfg config.ProtocolConfiguration
|
||||
standbyKeys keys.PublicKeys
|
||||
}
|
||||
|
||||
registerPrice atomic.Value
|
||||
registerPriceChanged atomic.Value
|
||||
type NeoCache struct {
|
||||
// gasPerBlock represents the history of generated gas per block.
|
||||
gasPerBlock gasRecord
|
||||
|
||||
votesChanged atomic.Value
|
||||
nextValidators atomic.Value
|
||||
validators atomic.Value
|
||||
registerPrice int64
|
||||
|
||||
votesChanged bool
|
||||
nextValidators keys.PublicKeys
|
||||
validators keys.PublicKeys
|
||||
// committee contains cached committee members and their votes.
|
||||
// It is updated once in a while depending on committee size
|
||||
// (every 28 blocks for mainnet). It's value
|
||||
// is always equal to value stored by `prefixCommittee`.
|
||||
committee atomic.Value
|
||||
committee keysWithVotes
|
||||
// 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.
|
||||
// It is set in state-modifying methods only and read in `PostPersist` thus is not protected
|
||||
// by any mutex.
|
||||
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 (
|
||||
|
@ -105,6 +104,41 @@ var (
|
|||
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.
|
||||
func makeValidatorKey(key *keys.PublicKey) []byte {
|
||||
b := key.Bytes()
|
||||
|
@ -116,7 +150,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
|
|||
}
|
||||
|
||||
// newNEO returns NEO native contract.
|
||||
func newNEO() *NEO {
|
||||
func newNEO(cfg config.ProtocolConfiguration) *NEO {
|
||||
n := &NEO{}
|
||||
defer n.UpdateHash()
|
||||
|
||||
|
@ -128,13 +162,11 @@ func newNEO() *NEO {
|
|||
nep17.balFromBytes = n.balanceFromBytes
|
||||
|
||||
n.nep17TokenNative = *nep17
|
||||
n.votesChanged.Store(true)
|
||||
n.nextValidators.Store(keys.PublicKeys(nil))
|
||||
n.validators.Store(keys.PublicKeys(nil))
|
||||
n.committee.Store(keysWithVotes(nil))
|
||||
n.committeeHash.Store(util.Uint160{})
|
||||
n.registerPriceChanged.Store(true)
|
||||
n.gasPerVoteCache = make(map[string]big.Int)
|
||||
|
||||
err := n.initConfigCache(cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to initialize NEO config cache: %w", err))
|
||||
}
|
||||
|
||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||
|
@ -198,10 +230,6 @@ func newNEO() *NEO {
|
|||
|
||||
// Initialize initializes NEO contract.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -211,9 +239,17 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
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)]
|
||||
cvs := toKeysWithVotes(committee0)
|
||||
err = n.updateCache(cvs, ic.Chain)
|
||||
err := n.updateCache(cache, cvs, ic.BlockHeight())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -231,80 +267,79 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
n.putGASRecord(ic.DAO, index, value)
|
||||
|
||||
gr := &gasRecord{{Index: index, GASPerBlock: *value}}
|
||||
n.gasPerBlock.Store(*gr)
|
||||
n.gasPerBlockChanged.Store(false)
|
||||
cache.gasPerBlock = *gr
|
||||
ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{})
|
||||
|
||||
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
|
||||
n.registerPrice.Store(int64(DefaultRegisterPrice))
|
||||
n.registerPriceChanged.Store(false)
|
||||
cache.registerPrice = int64(DefaultRegisterPrice)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache initializes all NEO cache with the proper values from storage.
|
||||
// Cache initialisation should be done apart from Initialize because Initialize is
|
||||
// called only when deploying native contracts.
|
||||
func (n *NEO) InitializeCache(bc interop.Ledger, d *dao.Simple) error {
|
||||
err := n.initConfigCache(bc)
|
||||
if err != nil {
|
||||
return nil
|
||||
func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &NeoCache{
|
||||
gasPerVoteCache: make(map[string]big.Int),
|
||||
votesChanged: true,
|
||||
}
|
||||
|
||||
var committee = keysWithVotes{}
|
||||
si := d.GetStorageItem(n.ID, prefixCommittee)
|
||||
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 {
|
||||
return err
|
||||
if err := n.updateCache(cache, committee, blockHeight); err != nil {
|
||||
return fmt.Errorf("failed to update cache: %w", err)
|
||||
}
|
||||
|
||||
n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d))
|
||||
n.gasPerBlockChanged.Store(false)
|
||||
cache.gasPerBlock = n.getSortedGASRecordFromDAO(d)
|
||||
cache.registerPrice = getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
|
||||
|
||||
d.SetCache(n.ID, cache)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NEO) initConfigCache(bc interop.Ledger) error {
|
||||
func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error {
|
||||
var err error
|
||||
|
||||
n.cfg = bc.GetConfig()
|
||||
n.cfg = cfg
|
||||
n.standbyKeys, err = keys.NewPublicKeysFromStrings(n.cfg.StandbyCommittee)
|
||||
return err
|
||||
}
|
||||
|
||||
func (n *NEO) updateCache(cvs keysWithVotes, bc interop.Ledger) error {
|
||||
n.committee.Store(cvs)
|
||||
func (n *NEO) updateCache(cache *NeoCache, cvs keysWithVotes, blockHeight uint32) error {
|
||||
cache.committee = cvs
|
||||
|
||||
var committee = n.GetCommitteeMembers()
|
||||
var committee = getCommitteeMembers(cache)
|
||||
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
||||
if err != nil {
|
||||
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)
|
||||
n.nextValidators.Store(nextVals)
|
||||
cache.nextValidators = nextVals
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NEO) updateCommittee(ic *interop.Context) error {
|
||||
votesChanged := n.votesChanged.Load().(bool)
|
||||
if !votesChanged {
|
||||
func (n *NEO) updateCommittee(cache *NeoCache, ic *interop.Context) error {
|
||||
if !cache.votesChanged {
|
||||
// We need to put in storage anyway, as it affects dumps
|
||||
committee := n.committee.Load().(keysWithVotes)
|
||||
ic.DAO.PutStorageItem(n.ID, prefixCommittee, committee.Bytes())
|
||||
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
_, cvs, err := n.computeCommitteeMembers(ic.Chain, ic.DAO)
|
||||
_, cvs, err := n.computeCommitteeMembers(ic.BlockHeight(), ic.DAO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.updateCache(cvs, ic.Chain); err != nil {
|
||||
if err := n.updateCache(cache, cvs, ic.BlockHeight()); err != nil {
|
||||
return err
|
||||
}
|
||||
n.votesChanged.Store(false)
|
||||
cache.votesChanged = false
|
||||
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
@ -312,13 +347,14 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
|
|||
// OnPersist implements Contract interface.
|
||||
func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
|
||||
oldKeys := n.nextValidators.Load().(keys.PublicKeys)
|
||||
oldCom := n.committee.Load().(keysWithVotes)
|
||||
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||
oldKeys := cache.nextValidators
|
||||
oldCom := cache.committee
|
||||
if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) ||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +364,8 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
|||
// PostPersist implements Contract interface.
|
||||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||
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)
|
||||
index := int(ic.Block.Index) % committeeSize
|
||||
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, big100)
|
||||
|
||||
var cs = n.committee.Load().(keysWithVotes)
|
||||
var key = make([]byte, 38)
|
||||
var (
|
||||
cs = cache.committee
|
||||
isCacheRW bool
|
||||
key = make([]byte, 38)
|
||||
)
|
||||
for i := range cs {
|
||||
if cs[i].Votes.Sign() > 0 {
|
||||
var tmp = new(big.Int)
|
||||
|
@ -358,7 +398,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
|||
key = makeVoterKey([]byte(cs[i].Key), key)
|
||||
|
||||
var r *big.Int
|
||||
if g, ok := n.gasPerVoteCache[cs[i].Key]; ok {
|
||||
if g, ok := cache.gasPerVoteCache[cs[i].Key]; ok {
|
||||
r = &g
|
||||
} else {
|
||||
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)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -504,28 +538,25 @@ func (n *NEO) getSortedGASRecordFromDAO(d *dao.Simple) gasRecord {
|
|||
|
||||
// GetGASPerBlock returns gas generated for block with provided index.
|
||||
func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int {
|
||||
var gr gasRecord
|
||||
if n.gasPerBlockChanged.Load().(bool) {
|
||||
gr = n.getSortedGASRecordFromDAO(d)
|
||||
} else {
|
||||
gr = n.gasPerBlock.Load().(gasRecord)
|
||||
}
|
||||
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||
gr := cache.gasPerBlock
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
if gr[i].Index <= index {
|
||||
g := gr[i].GASPerBlock
|
||||
return &g
|
||||
}
|
||||
}
|
||||
panic("contract not initialized")
|
||||
panic("NEO cache not initialized")
|
||||
}
|
||||
|
||||
// GetCommitteeAddress returns address of the committee.
|
||||
func (n *NEO) GetCommitteeAddress() util.Uint160 {
|
||||
return n.committeeHash.Load().(util.Uint160)
|
||||
func (n *NEO) GetCommitteeAddress(d *dao.Simple) util.Uint160 {
|
||||
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||
return cache.committeeHash
|
||||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -549,8 +580,12 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) er
|
|||
if !n.checkCommittee(ic) {
|
||||
return errors.New("invalid committee signature")
|
||||
}
|
||||
n.gasPerBlockChanged.Store(true)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -559,10 +594,8 @@ func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackite
|
|||
}
|
||||
|
||||
func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 {
|
||||
if !n.registerPriceChanged.Load().(bool) {
|
||||
return n.registerPrice.Load().(int64)
|
||||
}
|
||||
return getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
|
||||
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||
return cache.registerPrice
|
||||
}
|
||||
|
||||
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())
|
||||
n.registerPriceChanged.Store(true)
|
||||
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
||||
cache.registerPrice = price.Int64()
|
||||
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 {
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
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.
|
||||
return true
|
||||
})
|
||||
delete(n.gasPerVoteCache, string(voterKey))
|
||||
delete(cache.gasPerVoteCache, string(voterKey))
|
||||
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.New("negative value")
|
||||
}
|
||||
var gr gasRecord
|
||||
if !n.gasPerBlockChanged.Load().(bool) {
|
||||
gr = n.gasPerBlock.Load().(gasRecord)
|
||||
} else {
|
||||
gr = n.getSortedGASRecordFromDAO(d)
|
||||
}
|
||||
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||
gr := cache.gasPerBlock
|
||||
var sum, tmp big.Int
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
if gr[i].Index >= end {
|
||||
|
@ -719,12 +749,13 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
|
|||
if si == 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.Registered = false
|
||||
ok, err := n.dropCandidateIfZero(ic.DAO, pub, c)
|
||||
ok := n.dropCandidateIfZero(ic.DAO, cache, pub, c)
|
||||
if ok {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
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).
|
||||
// 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 {
|
||||
n.votesChanged.Store(true)
|
||||
cache := d.GetRWCache(n.ID).(*NeoCache)
|
||||
cache.votesChanged = true
|
||||
if acc.VoteTo != nil {
|
||||
key := makeValidatorKey(acc.VoteTo)
|
||||
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.Votes.Add(&cd.Votes, value)
|
||||
if !isNewVote {
|
||||
ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd)
|
||||
ok := n.dropCandidateIfZero(d, cache, acc.VoteTo, cd)
|
||||
if ok {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
n.validators.Store(keys.PublicKeys(nil))
|
||||
cache.validators = nil
|
||||
return putConvertibleToDAO(n.ID, d, key, cd)
|
||||
}
|
||||
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 {
|
||||
c := new(candidate).FromBytes(v)
|
||||
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})
|
||||
}
|
||||
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.
|
||||
func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, error) {
|
||||
numOfCNs := n.cfg.GetNumOfCNs(bc.BlockHeight() + 1)
|
||||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil && numOfCNs == len(vals) {
|
||||
func (n *NEO) ComputeNextBlockValidators(blockHeight uint32, d *dao.Simple) (keys.PublicKeys, error) {
|
||||
numOfCNs := n.cfg.GetNumOfCNs(blockHeight + 1)
|
||||
// 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
|
||||
}
|
||||
result, _, err := n.computeCommitteeMembers(bc, d)
|
||||
cache = d.GetRWCache(n.ID).(*NeoCache)
|
||||
result, _, err := n.computeCommitteeMembers(blockHeight, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = result[:numOfCNs]
|
||||
sort.Sort(result)
|
||||
n.validators.Store(result)
|
||||
cache.validators = result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
pubs := n.GetCommitteeMembers()
|
||||
pubs := n.GetCommitteeMembers(ic.DAO)
|
||||
sort.Sort(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.
|
||||
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
|
||||
var cvs = n.committee.Load().(keysWithVotes)
|
||||
func (n *NEO) GetCommitteeMembers(d *dao.Simple) keys.PublicKeys {
|
||||
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 err error
|
||||
for i := range committee {
|
||||
|
@ -965,7 +1006,7 @@ func toKeysWithVotes(pubs keys.PublicKeys) keysWithVotes {
|
|||
}
|
||||
|
||||
// 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}
|
||||
si := d.GetStorageItem(n.ID, key)
|
||||
if si == nil {
|
||||
|
@ -977,7 +1018,7 @@ func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d *dao.Simple) (keys.Pu
|
|||
_, totalSupply := n.getTotalSupply(d)
|
||||
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.
|
||||
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
||||
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 {
|
||||
result := n.GetNextBlockValidatorsInternal()
|
||||
result := n.GetNextBlockValidatorsInternal(ic.DAO)
|
||||
return pubsToArray(result)
|
||||
}
|
||||
|
||||
// GetNextBlockValidatorsInternal returns next block validators.
|
||||
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
|
||||
return n.nextValidators.Load().(keys.PublicKeys).Copy()
|
||||
func (n *NEO) GetNextBlockValidatorsInternal(d *dao.Simple) keys.PublicKeys {
|
||||
cache := d.GetROCache(n.ID).(*NeoCache)
|
||||
return cache.nextValidators.Copy()
|
||||
}
|
||||
|
||||
// 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/state"
|
||||
"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/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/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) {
|
||||
pubs := make([]interface{}, len(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/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/io"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -45,3 +52,88 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
|
|||
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/manifest"
|
||||
"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/opcode"
|
||||
"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)
|
||||
}
|
||||
|
||||
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) {
|
||||
c := newManagementClient(t)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestNEO_GasPerBlockCache(t *testing.T) {
|
||||
testGetSetCache(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor)
|
||||
}
|
||||
|
||||
func TestNEO_RegisterPrice(t *testing.T) {
|
||||
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) {
|
||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||
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))
|
||||
}
|
||||
|
||||
func TestNotary_MaxNotValidBeforeDeltaCache(t *testing.T) {
|
||||
c := newNotaryClient(t)
|
||||
testGetSetCache(t, c, "MaxNotValidBeforeDelta", 140)
|
||||
}
|
||||
|
||||
func TestNotary_NotaryServiceFeePerKey(t *testing.T) {
|
||||
c := newNotaryClient(t)
|
||||
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) {
|
||||
notaryCommitteeInvoker := newNotaryClient(t)
|
||||
e := notaryCommitteeInvoker.Executor
|
||||
|
|
|
@ -28,10 +28,14 @@ func newOracleClient(t *testing.T) *neotest.ContractInvoker {
|
|||
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)
|
||||
}
|
||||
|
||||
func TestOracle_GetSetPriceCache(t *testing.T) {
|
||||
testGetSetCache(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice)
|
||||
}
|
||||
|
||||
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
|
||||
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
|
||||
var filtItem interface{}
|
||||
|
|
|
@ -19,14 +19,26 @@ func TestPolicy_FeePerByte(t *testing.T) {
|
|||
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) {
|
||||
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) {
|
||||
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) {
|
||||
c := newPolicyClient(t)
|
||||
e := c.Executor
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
|
@ -32,12 +31,9 @@ type Notary struct {
|
|||
GAS *GAS
|
||||
NEO *NEO
|
||||
Desig *Designate
|
||||
}
|
||||
|
||||
lock sync.RWMutex
|
||||
// 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
|
||||
type NotaryCache struct {
|
||||
maxNotValidBeforeDelta uint32
|
||||
notaryServiceFeePerKey int64
|
||||
}
|
||||
|
@ -56,6 +52,22 @@ var (
|
|||
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.
|
||||
func newNotary() *Notary {
|
||||
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 {
|
||||
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
|
||||
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey)
|
||||
n.isValid = true
|
||||
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta
|
||||
n.notaryServiceFeePerKey = defaultNotaryServiceFeePerKey
|
||||
|
||||
cache := &NotaryCache{
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -176,15 +201,6 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
|||
|
||||
// PostPersist implements Contract interface.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -207,7 +223,7 @@ func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem
|
|||
}
|
||||
|
||||
allowedChangeTill := ic.Tx.Sender() == to
|
||||
currentHeight := ic.Chain.BlockHeight()
|
||||
currentHeight := ic.BlockHeight()
|
||||
deposit := n.GetDepositFor(ic.DAO, to)
|
||||
till := toUint32(additionalParams[1])
|
||||
if till < currentHeight {
|
||||
|
@ -250,7 +266,7 @@ func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) st
|
|||
return stackitem.NewBool(false)
|
||||
}
|
||||
till := toUint32(args[1])
|
||||
if till < ic.Chain.BlockHeight() {
|
||||
if till < ic.BlockHeight() {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
deposit := n.GetDepositFor(ic.DAO, addr)
|
||||
|
@ -286,7 +302,7 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.
|
|||
if deposit == nil {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
if ic.Chain.BlockHeight() < deposit.Till {
|
||||
if ic.BlockHeight() < deposit.Till {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
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.
|
||||
func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
if n.isValid {
|
||||
return n.maxNotValidBeforeDelta
|
||||
}
|
||||
return uint32(getIntWithKey(n.ID, dao, maxNotValidBeforeDeltaKey))
|
||||
cache := dao.GetROCache(n.ID).(*NotaryCache)
|
||||
return cache.maxNotValidBeforeDelta
|
||||
}
|
||||
|
||||
// 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])
|
||||
cfg := ic.Chain.GetConfig()
|
||||
maxInc := cfg.MaxValidUntilBlockIncrement
|
||||
if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.Chain.BlockHeight())) {
|
||||
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, 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.BlockHeight())))
|
||||
}
|
||||
if !n.NEO.checkCommittee(ic) {
|
||||
panic("invalid committee signature")
|
||||
}
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value))
|
||||
n.isValid = false
|
||||
cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache)
|
||||
cache.maxNotValidBeforeDelta = value
|
||||
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.
|
||||
func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
if n.isValid {
|
||||
return n.notaryServiceFeePerKey
|
||||
}
|
||||
return getIntWithKey(n.ID, dao, notaryServiceFeeKey)
|
||||
cache := dao.GetROCache(n.ID).(*NotaryCache)
|
||||
return cache.notaryServiceFeePerKey
|
||||
}
|
||||
|
||||
// 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) {
|
||||
panic("invalid committee signature")
|
||||
}
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value))
|
||||
n.isValid = false
|
||||
cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache)
|
||||
cache.notaryServiceFeePerKey = value
|
||||
return stackitem.Null{}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,15 +40,16 @@ type Oracle struct {
|
|||
Desig *Designate
|
||||
oracleScript []byte
|
||||
|
||||
requestPrice atomic.Value
|
||||
requestPriceChanged atomic.Value
|
||||
|
||||
// Module is an oracle module capable of talking with the external world.
|
||||
Module atomic.Value
|
||||
// newRequests contains new requests created during current block.
|
||||
newRequests map[uint64]*state.OracleRequest
|
||||
}
|
||||
|
||||
type OracleCache struct {
|
||||
requestPrice int64
|
||||
}
|
||||
|
||||
const (
|
||||
oracleContractID = -9
|
||||
maxURLLength = 256
|
||||
|
@ -82,6 +83,22 @@ var (
|
|||
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 {
|
||||
o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)}
|
||||
defer o.UpdateHash()
|
||||
|
@ -121,8 +138,6 @@ func newOracle() *Oracle {
|
|||
md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States)
|
||||
o.AddMethod(md, desc)
|
||||
|
||||
o.requestPriceChanged.Store(true)
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
|
@ -143,10 +158,6 @@ func (o *Oracle) OnPersist(ic *interop.Context) error {
|
|||
// PostPersist represents `postPersist` method.
|
||||
func (o *Oracle) PostPersist(ic *interop.Context) error {
|
||||
p := o.getPriceInternal(ic.DAO)
|
||||
if o.requestPriceChanged.Load().(bool) {
|
||||
o.requestPrice.Store(p)
|
||||
o.requestPriceChanged.Store(false)
|
||||
}
|
||||
|
||||
var nodes keys.PublicKeys
|
||||
var reward []big.Int
|
||||
|
@ -220,11 +231,20 @@ func (o *Oracle) Metadata() *interop.ContractMD {
|
|||
func (o *Oracle) Initialize(ic *interop.Context) error {
|
||||
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
for i := range tx.Attributes {
|
||||
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 {
|
||||
if !o.requestPriceChanged.Load().(bool) {
|
||||
return o.requestPrice.Load().(int64)
|
||||
}
|
||||
return getIntWithKey(o.ID, d, prefixRequestPrice)
|
||||
cache := d.GetROCache(o.ID).(*OracleCache)
|
||||
return cache.requestPrice
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
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{}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
|
@ -52,12 +51,10 @@ var (
|
|||
// Policy represents Policy native contract.
|
||||
type Policy struct {
|
||||
interop.ContractMD
|
||||
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
|
||||
// blockchain DAO persisting. If true, we can safely use cached values.
|
||||
isValid bool
|
||||
NEO *NEO
|
||||
}
|
||||
|
||||
type PolicyCache struct {
|
||||
execFeeFactor uint32
|
||||
feePerByte int64
|
||||
maxVerificationGas int64
|
||||
|
@ -65,7 +62,23 @@ type Policy struct {
|
|||
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.
|
||||
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, storagePriceKey, DefaultStoragePrice)
|
||||
|
||||
p.isValid = true
|
||||
p.execFeeFactor = defaultExecFeeFactor
|
||||
p.feePerByte = defaultFeePerByte
|
||||
p.maxVerificationGas = defaultMaxVerificationGas
|
||||
p.storagePrice = DefaultStoragePrice
|
||||
p.blockedAccounts = make([]util.Uint160, 0)
|
||||
cache := &PolicyCache{
|
||||
execFeeFactor: defaultExecFeeFactor,
|
||||
feePerByte: defaultFeePerByte,
|
||||
maxVerificationGas: defaultMaxVerificationGas,
|
||||
storagePrice: DefaultStoragePrice,
|
||||
blockedAccounts: make([]util.Uint160, 0),
|
||||
}
|
||||
ic.DAO.SetCache(p.ID, cache)
|
||||
|
||||
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.
|
||||
func (p *Policy) OnPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
|
@ -145,32 +193,7 @@ func (p *Policy) OnPersist(ic *interop.Context) error {
|
|||
|
||||
// PostPersist implements Contract interface.
|
||||
func (p *Policy) PostPersist(ic *interop.Context) error {
|
||||
p.lock.Lock()
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
if p.isValid {
|
||||
return p.feePerByte
|
||||
}
|
||||
return getIntWithKey(p.ID, dao, feePerByteKey)
|
||||
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||
return cache.feePerByte
|
||||
}
|
||||
|
||||
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
|
||||
func (p *Policy) GetMaxVerificationGas(_ *dao.Simple) int64 {
|
||||
if p.isValid {
|
||||
return p.maxVerificationGas
|
||||
}
|
||||
return defaultMaxVerificationGas
|
||||
func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 {
|
||||
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||
return cache.maxVerificationGas
|
||||
}
|
||||
|
||||
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.
|
||||
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
if p.isValid {
|
||||
return int64(p.execFeeFactor)
|
||||
}
|
||||
return getIntWithKey(p.ID, d, execFeeFactorKey)
|
||||
cache := d.GetROCache(p.ID).(*PolicyCache)
|
||||
return int64(cache.execFeeFactor)
|
||||
}
|
||||
|
||||
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) {
|
||||
panic("invalid committee signature")
|
||||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
|
||||
p.isValid = false
|
||||
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||
cache.execFeeFactor = value
|
||||
return stackitem.Null{}
|
||||
}
|
||||
|
||||
// isBlocked is Policy contract method and checks whether provided account is blocked.
|
||||
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
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.
|
||||
func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
if p.isValid {
|
||||
length := len(p.blockedAccounts)
|
||||
i := sort.Search(length, func(i int) bool {
|
||||
return !p.blockedAccounts[i].Less(hash)
|
||||
})
|
||||
if length != 0 && i != length && p.blockedAccounts[i].Equals(hash) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// IsBlocked checks whether provided account is blocked.
|
||||
func (p *Policy) IsBlocked(dao *dao.Simple, hash util.Uint160) bool {
|
||||
_, isBlocked := p.isBlockedInternal(dao, hash)
|
||||
return isBlocked
|
||||
}
|
||||
|
||||
// isBlockedInternal checks whether provided account is blocked. It returns position
|
||||
// of the blocked account in the blocked accounts list (or the position it should be
|
||||
// put at).
|
||||
func (p *Policy) isBlockedInternal(dao *dao.Simple, hash util.Uint160) (int, bool) {
|
||||
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||
length := len(cache.blockedAccounts)
|
||||
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 dao.GetStorageItem(p.ID, key) != nil
|
||||
return i, false
|
||||
}
|
||||
|
||||
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.
|
||||
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
if p.isValid {
|
||||
return int64(p.storagePrice)
|
||||
}
|
||||
return getIntWithKey(p.ID, d, storagePriceKey)
|
||||
cache := d.GetROCache(p.ID).(*PolicyCache)
|
||||
return int64(cache.storagePrice)
|
||||
}
|
||||
|
||||
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) {
|
||||
panic("invalid committee signature")
|
||||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
|
||||
p.isValid = false
|
||||
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||
cache.storagePrice = value
|
||||
return stackitem.Null{}
|
||||
}
|
||||
|
||||
|
@ -288,10 +299,9 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
|||
if !p.NEO.checkCommittee(ic) {
|
||||
panic("invalid committee signature")
|
||||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
|
||||
p.isValid = false
|
||||
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
||||
cache.feePerByte = value
|
||||
return stackitem.Null{}
|
||||
}
|
||||
|
||||
|
@ -307,14 +317,19 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
|
|||
panic("cannot block native contract")
|
||||
}
|
||||
}
|
||||
if p.IsBlockedInternal(ic.DAO, hash) {
|
||||
i, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||
if blocked {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -325,14 +340,14 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
|||
panic("invalid committee signature")
|
||||
}
|
||||
hash := toUint160(args[0])
|
||||
if !p.IsBlockedInternal(ic.DAO, hash) {
|
||||
i, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||
if !blocked {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -341,7 +356,7 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
|||
// fee limit.
|
||||
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package stateroot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -101,6 +102,34 @@ func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) {
|
|||
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.
|
||||
func (s *Module) CurrentLocalStateRoot() 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, 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 {
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
err = tempstore.ps.PutChangeSet(tempstore.mem, tempstore.stor)
|
||||
|
||||
if !isSync {
|
||||
|
|
|
@ -588,6 +588,33 @@ func (c *Client) InvokeScript(script []byte, signers []transaction.Signer) (*res
|
|||
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
|
||||
// with the given operation and parameters.
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// with the given parameters under verification trigger type.
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
var resp = new(result.Invoke)
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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/native/nativenames"
|
||||
"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))
|
||||
})
|
||||
|
||||
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) {
|
||||
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)
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"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/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/mempoolevent"
|
||||
"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){
|
||||
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
||||
"findstates": (*Server).findStates,
|
||||
"getapplicationlog": (*Server).getApplicationLog,
|
||||
"getbestblockhash": (*Server).getBestBlockHash,
|
||||
"getblock": (*Server).getBlock,
|
||||
"getblockcount": (*Server).getBlockCount,
|
||||
"getblockhash": (*Server).getBlockHash,
|
||||
"getblockheader": (*Server).getBlockHeader,
|
||||
"getblockheadercount": (*Server).getBlockHeaderCount,
|
||||
"getblocksysfee": (*Server).getBlockSysFee,
|
||||
"getcommittee": (*Server).getCommittee,
|
||||
"getconnectioncount": (*Server).getConnectionCount,
|
||||
"getcontractstate": (*Server).getContractState,
|
||||
"getnativecontracts": (*Server).getNativeContracts,
|
||||
"getnep11balances": (*Server).getNEP11Balances,
|
||||
"getnep11properties": (*Server).getNEP11Properties,
|
||||
"getnep11transfers": (*Server).getNEP11Transfers,
|
||||
"getnep17balances": (*Server).getNEP17Balances,
|
||||
"getnep17transfers": (*Server).getNEP17Transfers,
|
||||
"getpeers": (*Server).getPeers,
|
||||
"getproof": (*Server).getProof,
|
||||
"getrawmempool": (*Server).getRawMempool,
|
||||
"getrawtransaction": (*Server).getrawtransaction,
|
||||
"getstate": (*Server).getState,
|
||||
"getstateheight": (*Server).getStateHeight,
|
||||
"getstateroot": (*Server).getStateRoot,
|
||||
"getstorage": (*Server).getStorage,
|
||||
"gettransactionheight": (*Server).getTransactionHeight,
|
||||
"getunclaimedgas": (*Server).getUnclaimedGas,
|
||||
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
||||
"getversion": (*Server).getVersion,
|
||||
"invokefunction": (*Server).invokeFunction,
|
||||
"invokescript": (*Server).invokescript,
|
||||
"invokecontractverify": (*Server).invokeContractVerify,
|
||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||
"submitblock": (*Server).submitBlock,
|
||||
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
||||
"submitoracleresponse": (*Server).submitOracleResponse,
|
||||
"validateaddress": (*Server).validateAddress,
|
||||
"verifyproof": (*Server).verifyProof,
|
||||
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
||||
"findstates": (*Server).findStates,
|
||||
"getapplicationlog": (*Server).getApplicationLog,
|
||||
"getbestblockhash": (*Server).getBestBlockHash,
|
||||
"getblock": (*Server).getBlock,
|
||||
"getblockcount": (*Server).getBlockCount,
|
||||
"getblockhash": (*Server).getBlockHash,
|
||||
"getblockheader": (*Server).getBlockHeader,
|
||||
"getblockheadercount": (*Server).getBlockHeaderCount,
|
||||
"getblocksysfee": (*Server).getBlockSysFee,
|
||||
"getcommittee": (*Server).getCommittee,
|
||||
"getconnectioncount": (*Server).getConnectionCount,
|
||||
"getcontractstate": (*Server).getContractState,
|
||||
"getnativecontracts": (*Server).getNativeContracts,
|
||||
"getnep11balances": (*Server).getNEP11Balances,
|
||||
"getnep11properties": (*Server).getNEP11Properties,
|
||||
"getnep11transfers": (*Server).getNEP11Transfers,
|
||||
"getnep17balances": (*Server).getNEP17Balances,
|
||||
"getnep17transfers": (*Server).getNEP17Transfers,
|
||||
"getpeers": (*Server).getPeers,
|
||||
"getproof": (*Server).getProof,
|
||||
"getrawmempool": (*Server).getRawMempool,
|
||||
"getrawtransaction": (*Server).getrawtransaction,
|
||||
"getstate": (*Server).getState,
|
||||
"getstateheight": (*Server).getStateHeight,
|
||||
"getstateroot": (*Server).getStateRoot,
|
||||
"getstorage": (*Server).getStorage,
|
||||
"gettransactionheight": (*Server).getTransactionHeight,
|
||||
"getunclaimedgas": (*Server).getUnclaimedGas,
|
||||
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
||||
"getversion": (*Server).getVersion,
|
||||
"invokefunction": (*Server).invokeFunction,
|
||||
"invokefunctionhistoric": (*Server).invokeFunctionHistoric,
|
||||
"invokescript": (*Server).invokescript,
|
||||
"invokescripthistoric": (*Server).invokescripthistoric,
|
||||
"invokecontractverify": (*Server).invokeContractVerify,
|
||||
"invokecontractverifyhistoric": (*Server).invokeContractVerifyHistoric,
|
||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||
"submitblock": (*Server).submitBlock,
|
||||
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
||||
"submitoracleresponse": (*Server).submitOracleResponse,
|
||||
"validateaddress": (*Server).validateAddress,
|
||||
"verifyproof": (*Server).verifyProof,
|
||||
}
|
||||
|
||||
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()
|
||||
tx := &transaction.Transaction{Script: script}
|
||||
b, err := s.getFakeNextBlock()
|
||||
b, err := s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -1571,16 +1575,40 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
|
|||
|
||||
// invokeFunction implements the `invokeFunction` RPC call.
|
||||
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 {
|
||||
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))
|
||||
if responseErr != nil {
|
||||
return nil, responseErr
|
||||
return nil, false, responseErr
|
||||
}
|
||||
method, err := reqParams[1].GetString()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
var params *request.Param
|
||||
if len(reqParams) > 2 {
|
||||
|
@ -1590,7 +1618,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
if len(reqParams) > 3 {
|
||||
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
}
|
||||
|
@ -1598,7 +1626,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
if len(reqParams) > 4 {
|
||||
verbose, err = reqParams[4].GetBoolean()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
}
|
||||
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)
|
||||
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
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
||||
return tx, verbose, nil
|
||||
}
|
||||
|
||||
// invokescript implements the `invokescript` RPC call.
|
||||
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
|
||||
}
|
||||
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 {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
tx := &transaction.Transaction{}
|
||||
if len(reqParams) > 1 {
|
||||
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
tx.Scripts = witnesses
|
||||
|
@ -1636,33 +1684,57 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
|||
if len(reqParams) > 2 {
|
||||
verbose, err = reqParams[2].GetBoolean()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
}
|
||||
if len(tx.Signers) == 0 {
|
||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
||||
return tx, verbose, nil
|
||||
}
|
||||
|
||||
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||
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))
|
||||
if responseErr != nil {
|
||||
return nil, responseErr
|
||||
return util.Uint160{}, nil, nil, responseErr
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
if len(reqParams) > 1 {
|
||||
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
||||
if err != nil {
|
||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||
return util.Uint160{}, nil, nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
||||
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 {
|
||||
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return util.Uint160{}, nil, nil, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
tx.Scripts = witnesses
|
||||
|
@ -1680,16 +1752,51 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
|||
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
||||
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,
|
||||
// because it depends on persisting tx height.
|
||||
// This is why we provide block here.
|
||||
b := block.New(s.stateRootEnabled)
|
||||
b.Index = s.chain.BlockHeight() + 1
|
||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight())))
|
||||
b.Index = nextBlockHeight
|
||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(nextBlockHeight - 1)))
|
||||
if err != nil {
|
||||
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`
|
||||
// arguments on stack before verification). In case of contract verification
|
||||
// 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) {
|
||||
b, err := s.getFakeNextBlock()
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't create fake block", err)
|
||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*result.Invoke, *response.Error) {
|
||||
var (
|
||||
err error
|
||||
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 {
|
||||
ic.VM.EnableInvocationTree()
|
||||
}
|
||||
|
@ -1720,9 +1838,9 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
|
|||
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 {
|
||||
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 {
|
||||
ic.VM.LoadScriptWithFlags(script, callflag.All)
|
||||
|
|
|
@ -75,6 +75,7 @@ const (
|
|||
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||
block20StateRootLE = "19ec3c3d01afe5274e8bb4a393c97da708c5608c5b0ad116c16108b6a04fb08e"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -999,6 +1000,134 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
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": {
|
||||
{
|
||||
name: "positive",
|
||||
|
@ -1098,6 +1227,132 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
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": {
|
||||
{
|
||||
name: "positive",
|
||||
|
@ -1203,6 +1458,129 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
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": {
|
||||
{
|
||||
name: "positive",
|
||||
|
|
Loading…
Reference in a new issue