bd400dfe20
It can be a pointer to cached data: 2021-07-07T20:05:24.8508647Z ================== 2021-07-07T20:05:24.8509294Z WARNING: DATA RACE 2021-07-07T20:05:24.8510243Z Write at 0x00c000142cc8 by goroutine 51: 2021-07-07T20:05:24.8511350Z github.com/nspcc-dev/neo-go/pkg/core/native.(*Management).Update() 2021-07-07T20:05:24.8529622Z /home/runner/work/neo-go/neo-go/pkg/core/native/management.go:335 +0x70a 2021-07-07T20:05:24.8531266Z github.com/nspcc-dev/neo-go/pkg/core/native.(*Management).updateWithData() 2021-07-07T20:05:24.8532544Z /home/runner/work/neo-go/neo-go/pkg/core/native/management.go:316 +0x1e9 2021-07-07T20:05:24.8533726Z github.com/nspcc-dev/neo-go/pkg/core/native.(*Management).update() 2021-07-07T20:05:24.8534928Z /home/runner/work/neo-go/neo-go/pkg/core/native/management.go:303 +0x111 2021-07-07T20:05:24.8536123Z github.com/nspcc-dev/neo-go/pkg/core/native.(*Management).update-fm() 2021-07-07T20:05:24.8537389Z /home/runner/work/neo-go/neo-go/pkg/core/native/management.go:302 +0x34 2021-07-07T20:05:24.8538462Z github.com/nspcc-dev/neo-go/pkg/core/native.Call() 2021-07-07T20:05:24.8539565Z /home/runner/work/neo-go/neo-go/pkg/core/native/interop.go:54 +0x90c 2021-07-07T20:05:24.8540751Z github.com/nspcc-dev/neo-go/pkg/core/interop.(*Context).SyscallHandler() 2021-07-07T20:05:24.8542177Z /home/runner/work/neo-go/neo-go/pkg/core/interop/context.go:262 +0x216 2021-07-07T20:05:24.8543328Z github.com/nspcc-dev/neo-go/pkg/core/interop.(*Context).SyscallHandler-fm() 2021-07-07T20:05:24.8544586Z /home/runner/work/neo-go/neo-go/pkg/core/interop/context.go:250 +0x53 2021-07-07T20:05:24.8545648Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).execute() 2021-07-07T20:05:24.8546635Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:1312 +0xb2d5 2021-07-07T20:05:24.8547632Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).Step() 2021-07-07T20:05:24.8548640Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:416 +0x1f2 2021-07-07T20:05:24.8549836Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).Run() 2021-07-07T20:05:24.8550852Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:393 +0x195 2021-07-07T20:05:24.8551989Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).runScriptInVM() 2021-07-07T20:05:24.8553174Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:1326 +0x7eb 2021-07-07T20:05:24.8554345Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).invokeFunction() 2021-07-07T20:05:24.8555560Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:1219 +0x50c 2021-07-07T20:05:24.8556616Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).handleIn() 2021-07-07T20:05:24.8557823Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:345 +0xecb 2021-07-07T20:05:24.8558987Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).handleRequest() 2021-07-07T20:05:24.8560356Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:315 +0x364 2021-07-07T20:05:24.8561599Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).handleHTTPRequest() 2021-07-07T20:05:24.8562976Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:309 +0xf9b 2021-07-07T20:05:24.8564208Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).handleHTTPRequest-fm() 2021-07-07T20:05:24.8565523Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:257 +0x64 2021-07-07T20:05:24.8566348Z net/http.HandlerFunc.ServeHTTP() 2021-07-07T20:05:24.8567290Z /opt/hostedtoolcache/go/1.15.13/x64/src/net/http/server.go:2042 +0x51 2021-07-07T20:05:24.8568185Z net/http.serverHandler.ServeHTTP() 2021-07-07T20:05:24.8569130Z /opt/hostedtoolcache/go/1.15.13/x64/src/net/http/server.go:2843 +0xca 2021-07-07T20:05:24.8569861Z net/http.(*conn).serve() 2021-07-07T20:05:24.8570634Z /opt/hostedtoolcache/go/1.15.13/x64/src/net/http/server.go:1925 +0x84c 2021-07-07T20:05:24.8571139Z 2021-07-07T20:05:24.8571639Z Previous read at 0x00c000142cc8 by goroutine 41: 2021-07-07T20:05:24.8572892Z github.com/nspcc-dev/neo-go/pkg/core/interop/contract.callExFromNative() 2021-07-07T20:05:24.8574265Z /home/runner/work/neo-go/neo-go/pkg/core/interop/contract/call.go:101 +0x2d4 2021-07-07T20:05:24.8575581Z github.com/nspcc-dev/neo-go/pkg/core/interop/contract.callInternal() 2021-07-07T20:05:24.8576951Z /home/runner/work/neo-go/neo-go/pkg/core/interop/contract/call.go:85 +0x1f5 2021-07-07T20:05:24.8578094Z github.com/nspcc-dev/neo-go/pkg/core/interop/contract.Call() 2021-07-07T20:05:24.8579306Z /home/runner/work/neo-go/neo-go/pkg/core/interop/contract/call.go:69 +0x804 2021-07-07T20:05:24.8580514Z github.com/nspcc-dev/neo-go/pkg/core/interop.(*Context).SyscallHandler() 2021-07-07T20:05:24.8581755Z /home/runner/work/neo-go/neo-go/pkg/core/interop/context.go:262 +0x216 2021-07-07T20:05:24.8582907Z github.com/nspcc-dev/neo-go/pkg/core/interop.(*Context).SyscallHandler-fm() 2021-07-07T20:05:24.8584300Z /home/runner/work/neo-go/neo-go/pkg/core/interop/context.go:250 +0x53 2021-07-07T20:05:24.8585422Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).execute() 2021-07-07T20:05:24.8586403Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:1312 +0xb2d5 2021-07-07T20:05:24.8590451Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).Step() 2021-07-07T20:05:24.8591549Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:416 +0x1f2 2021-07-07T20:05:24.8592466Z github.com/nspcc-dev/neo-go/pkg/vm.(*VM).Run() 2021-07-07T20:05:24.8593550Z /home/runner/work/neo-go/neo-go/pkg/vm/vm.go:393 +0x195 2021-07-07T20:05:24.8594531Z github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).storeBlock() 2021-07-07T20:05:24.8595708Z /home/runner/work/neo-go/neo-go/pkg/core/blockchain.go:717 +0x1694 2021-07-07T20:05:24.8596793Z github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).AddBlock() 2021-07-07T20:05:24.8598142Z /home/runner/work/neo-go/neo-go/pkg/core/blockchain.go:579 +0x544 2021-07-07T20:05:24.8599419Z github.com/nspcc-dev/neo-go/pkg/consensus.(*service).processBlock() 2021-07-07T20:05:24.8601093Z /home/runner/work/neo-go/neo-go/pkg/consensus/consensus.go:521 +0x141 2021-07-07T20:05:24.8602485Z github.com/nspcc-dev/neo-go/pkg/consensus.(*service).processBlock-fm() 2021-07-07T20:05:24.8603809Z /home/runner/work/neo-go/neo-go/pkg/consensus/consensus.go:517 +0x55 2021-07-07T20:05:24.8604910Z github.com/nspcc-dev/dbft.(*DBFT).checkCommit() 2021-07-07T20:05:24.8606098Z /home/runner/go/pkg/mod/github.com/nspcc-dev/dbft@v0.0.0-20210302103605-cc75991b7cfb/check.go:73 +0xd8a 2021-07-07T20:05:24.8607235Z github.com/nspcc-dev/dbft.(*DBFT).checkPrepare() 2021-07-07T20:05:24.8608551Z /home/runner/go/pkg/mod/github.com/nspcc-dev/dbft@v0.0.0-20210302103605-cc75991b7cfb/check.go:36 +0x677 2021-07-07T20:05:24.8609681Z github.com/nspcc-dev/dbft.(*DBFT).sendPrepareRequest() 2021-07-07T20:05:24.8611016Z /home/runner/go/pkg/mod/github.com/nspcc-dev/dbft@v0.0.0-20210302103605-cc75991b7cfb/send.go:42 +0x47c 2021-07-07T20:05:24.8612107Z github.com/nspcc-dev/dbft.(*DBFT).OnTimeout() 2021-07-07T20:05:24.8613257Z /home/runner/go/pkg/mod/github.com/nspcc-dev/dbft@v0.0.0-20210302103605-cc75991b7cfb/dbft.go:188 +0x68c 2021-07-07T20:05:24.8614499Z github.com/nspcc-dev/neo-go/pkg/consensus.(*service).eventLoop() 2021-07-07T20:05:24.8615712Z /home/runner/work/neo-go/neo-go/pkg/consensus/consensus.go:274 +0x17ed 2021-07-07T20:05:24.8616284Z 2021-07-07T20:05:24.8616757Z Goroutine 51 (running) created at: 2021-07-07T20:05:24.8617367Z net/http.(*Server).Serve() 2021-07-07T20:05:24.8618159Z /opt/hostedtoolcache/go/1.15.13/x64/src/net/http/server.go:2969 +0x5d3 2021-07-07T20:05:24.8619253Z github.com/nspcc-dev/neo-go/pkg/rpc/server.(*Server).Start.func2() 2021-07-07T20:05:24.8620437Z /home/runner/work/neo-go/neo-go/pkg/rpc/server/server.go:224 +0x85 2021-07-07T20:05:24.8620936Z 2021-07-07T20:05:24.8621405Z Goroutine 41 (running) created at: 2021-07-07T20:05:24.8622393Z github.com/nspcc-dev/neo-go/pkg/consensus.(*service).Start() 2021-07-07T20:05:24.8623562Z /home/runner/work/neo-go/neo-go/pkg/consensus/consensus.go:250 +0x17c 2021-07-07T20:05:24.8624682Z github.com/nspcc-dev/neo-go/pkg/network.(*Server).tryStartServices() 2021-07-07T20:05:24.8626078Z /home/runner/work/neo-go/neo-go/pkg/network/server.go:445 +0x2e1 2021-07-07T20:05:24.8627226Z github.com/nspcc-dev/neo-go/pkg/network.(*Server).Start() 2021-07-07T20:05:24.8628333Z /home/runner/work/neo-go/neo-go/pkg/network/server.go:268 +0x2af 2021-07-07T20:05:24.8628973Z ==================
573 lines
17 KiB
Go
573 lines
17 KiB
Go
package native
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"sync"
|
|
"unicode/utf8"
|
|
|
|
"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/native/nativenames"
|
|
"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/encoding/bigint"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Management is contract-managing native contract.
|
|
type Management struct {
|
|
interop.ContractMD
|
|
NEO *NEO
|
|
|
|
mtx sync.RWMutex
|
|
contracts map[util.Uint160]*state.Contract
|
|
}
|
|
|
|
const (
|
|
managementContractID = -1
|
|
|
|
prefixContract = 8
|
|
|
|
defaultMinimumDeploymentFee = 10_00000000
|
|
contractDeployNotificationName = "Deploy"
|
|
contractUpdateNotificationName = "Update"
|
|
contractDestroyNotificationName = "Destroy"
|
|
)
|
|
|
|
var (
|
|
errGasLimitExceeded = errors.New("gas limit exceeded")
|
|
|
|
keyNextAvailableID = []byte{15}
|
|
keyMinimumDeploymentFee = []byte{20}
|
|
)
|
|
|
|
// makeContractKey creates a key from account script hash.
|
|
func makeContractKey(h util.Uint160) []byte {
|
|
return makeUint160Key(prefixContract, h)
|
|
}
|
|
|
|
// newManagement creates new Management native contract.
|
|
func newManagement() *Management {
|
|
var m = &Management{
|
|
ContractMD: *interop.NewContractMD(nativenames.Management, managementContractID),
|
|
contracts: make(map[util.Uint160]*state.Contract),
|
|
}
|
|
defer m.UpdateHash()
|
|
|
|
desc := newDescriptor("getContract", smartcontract.ArrayType,
|
|
manifest.NewParameter("hash", smartcontract.Hash160Type))
|
|
md := newMethodAndPrice(m.getContract, 1<<15, callflag.ReadStates)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("deploy", smartcontract.ArrayType,
|
|
manifest.NewParameter("nefFile", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("manifest", smartcontract.ByteArrayType))
|
|
md = newMethodAndPrice(m.deploy, 0, callflag.States|callflag.AllowNotify)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("deploy", smartcontract.ArrayType,
|
|
manifest.NewParameter("nefFile", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("data", smartcontract.AnyType))
|
|
md = newMethodAndPrice(m.deployWithData, 0, callflag.States|callflag.AllowNotify)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("update", smartcontract.VoidType,
|
|
manifest.NewParameter("nefFile", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("manifest", smartcontract.ByteArrayType))
|
|
md = newMethodAndPrice(m.update, 0, callflag.States|callflag.AllowNotify)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("update", smartcontract.VoidType,
|
|
manifest.NewParameter("nefFile", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("data", smartcontract.AnyType))
|
|
md = newMethodAndPrice(m.updateWithData, 0, callflag.States|callflag.AllowNotify)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("destroy", smartcontract.VoidType)
|
|
md = newMethodAndPrice(m.destroy, 1<<15, callflag.States|callflag.AllowNotify)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("getMinimumDeploymentFee", smartcontract.IntegerType)
|
|
md = newMethodAndPrice(m.getMinimumDeploymentFee, 1<<15, callflag.ReadStates)
|
|
m.AddMethod(md, desc)
|
|
|
|
desc = newDescriptor("setMinimumDeploymentFee", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = newMethodAndPrice(m.setMinimumDeploymentFee, 1<<15, callflag.States)
|
|
m.AddMethod(md, desc)
|
|
|
|
hashParam := manifest.NewParameter("Hash", smartcontract.Hash160Type)
|
|
m.AddEvent(contractDeployNotificationName, hashParam)
|
|
m.AddEvent(contractUpdateNotificationName, hashParam)
|
|
m.AddEvent(contractDestroyNotificationName, hashParam)
|
|
return m
|
|
}
|
|
|
|
// getContract is an implementation of public getContract method, it's run under
|
|
// VM protections, so it's OK for it to panic instead of returning errors.
|
|
func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
hashBytes, err := args[0].TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
hash, err := util.Uint160DecodeBytesBE(hashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ctr, err := m.GetContract(ic.DAO, hash)
|
|
if err != nil {
|
|
if err == storage.ErrKeyNotFound {
|
|
return stackitem.Null{}
|
|
}
|
|
panic(err)
|
|
}
|
|
return contractToStack(ctr)
|
|
}
|
|
|
|
// GetContract returns contract with given hash from given DAO.
|
|
func (m *Management) GetContract(d dao.DAO, hash util.Uint160) (*state.Contract, error) {
|
|
m.mtx.RLock()
|
|
cs, ok := m.contracts[hash]
|
|
m.mtx.RUnlock()
|
|
if !ok {
|
|
return nil, storage.ErrKeyNotFound
|
|
} else if cs != nil {
|
|
return cs, nil
|
|
}
|
|
return m.getContractFromDAO(d, hash)
|
|
}
|
|
|
|
func (m *Management) getContractFromDAO(d dao.DAO, hash util.Uint160) (*state.Contract, error) {
|
|
contract := new(state.Contract)
|
|
key := makeContractKey(hash)
|
|
err := getSerializableFromDAO(m.ID, d, key, contract)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return contract, nil
|
|
}
|
|
|
|
func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
|
|
_, isNull := arg.(stackitem.Null)
|
|
if isNull {
|
|
return nil, nil
|
|
}
|
|
b, err := arg.TryBytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
l := len(b)
|
|
if l == 0 {
|
|
return nil, errors.New("empty")
|
|
} else if l > max {
|
|
return nil, fmt.Errorf("len is %d (max %d)", l, max)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// getNefAndManifestFromItems converts input arguments into NEF and manifest
|
|
// adding appropriate deployment GAS price and sanitizing inputs.
|
|
func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item, isDeploy bool) (*nef.File, *manifest.Manifest, error) {
|
|
nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization.
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid NEF file: %w", err)
|
|
}
|
|
manifestBytes, err := getLimitedSlice(args[1], manifest.MaxManifestSize)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid manifest: %w", err)
|
|
}
|
|
|
|
gas := ic.Chain.GetPolicer().GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes))
|
|
if isDeploy {
|
|
fee := m.GetMinimumDeploymentFee(ic.DAO)
|
|
if fee > gas {
|
|
gas = fee
|
|
}
|
|
}
|
|
if !ic.VM.AddGas(gas) {
|
|
return nil, nil, errGasLimitExceeded
|
|
}
|
|
var resManifest *manifest.Manifest
|
|
var resNef *nef.File
|
|
if nefBytes != nil {
|
|
nf, err := nef.FileFromBytes(nefBytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid NEF file: %w", err)
|
|
}
|
|
resNef = &nf
|
|
}
|
|
if manifestBytes != nil {
|
|
if !utf8.Valid(manifestBytes) {
|
|
return nil, nil, errors.New("manifest is not UTF-8 compliant")
|
|
}
|
|
resManifest = new(manifest.Manifest)
|
|
err := json.Unmarshal(manifestBytes, resManifest)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid manifest: %w", err)
|
|
}
|
|
}
|
|
return resNef, resManifest, nil
|
|
}
|
|
|
|
// deploy is an implementation of public 2-argument deploy method.
|
|
func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return m.deployWithData(ic, append(args, stackitem.Null{}))
|
|
}
|
|
|
|
// deployWithData is an implementation of public 3-argument deploy method.
|
|
// It's run under VM protections, so it's OK for it to panic instead of returning errors.
|
|
func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
neff, manif, err := m.getNefAndManifestFromItems(ic, args, true)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if neff == nil {
|
|
panic(errors.New("no valid NEF provided"))
|
|
}
|
|
if manif == nil {
|
|
panic(errors.New("no valid manifest provided"))
|
|
}
|
|
if ic.Tx == nil {
|
|
panic(errors.New("no transaction provided"))
|
|
}
|
|
newcontract, err := m.Deploy(ic.DAO, ic.Tx.Sender(), neff, manif)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
m.callDeploy(ic, newcontract, args[2], false)
|
|
m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash)
|
|
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()
|
|
}
|
|
|
|
// 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.DAO, 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 {
|
|
return nil, errors.New("contract already exists")
|
|
}
|
|
id, err := m.getNextContractID(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = manif.IsValid(h)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid manifest: %w", err)
|
|
}
|
|
err = checkScriptAndMethods(neff.Script, manif.ABI.Methods)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newcontract := &state.Contract{
|
|
ContractBase: state.ContractBase{
|
|
ID: id,
|
|
Hash: h,
|
|
NEF: *neff,
|
|
Manifest: *manif,
|
|
},
|
|
}
|
|
err = m.PutContractState(d, newcontract)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.markUpdated(newcontract.Hash)
|
|
return newcontract, nil
|
|
}
|
|
|
|
func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return m.updateWithData(ic, append(args, stackitem.Null{}))
|
|
}
|
|
|
|
// update is an implementation of public update method, it's run under
|
|
// VM protections, so it's OK for it to panic instead of returning errors.
|
|
func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
neff, manif, err := m.getNefAndManifestFromItems(ic, args, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if neff == nil && manif == nil {
|
|
panic(errors.New("both NEF and manifest are nil"))
|
|
}
|
|
contract, err := m.Update(ic.DAO, ic.VM.GetCallingScriptHash(), neff, manif)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
m.callDeploy(ic, contract, args[2], true)
|
|
m.emitNotification(ic, contractUpdateNotificationName, contract.Hash)
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// Update updates contract's script and/or manifest in the given DAO.
|
|
// It doesn't run _deploy method and doesn't emit notification.
|
|
func (m *Management) Update(d dao.DAO, hash util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
|
|
var contract state.Contract
|
|
|
|
oldcontract, err := m.GetContract(d, hash)
|
|
if err != nil {
|
|
return nil, errors.New("contract doesn't exist")
|
|
}
|
|
|
|
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
|
|
if manif != nil {
|
|
if manif.Name != contract.Manifest.Name {
|
|
return nil, errors.New("contract name can't be changed")
|
|
}
|
|
err = manif.IsValid(contract.Hash)
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contract.UpdateCounter++
|
|
err = m.PutContractState(d, &contract)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &contract, nil
|
|
}
|
|
|
|
// destroy is an implementation of destroy update method, it's run under
|
|
// VM protections, so it's OK for it to panic instead of returning errors.
|
|
func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackitem.Item {
|
|
hash := ic.VM.GetCallingScriptHash()
|
|
err := m.Destroy(ic.DAO, hash)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
m.emitNotification(ic, contractDestroyNotificationName, hash)
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// Destroy drops given contract from DAO along with its storage. It doesn't emit notification.
|
|
func (m *Management) Destroy(d dao.DAO, hash util.Uint160) error {
|
|
contract, err := m.GetContract(d, hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key := makeContractKey(hash)
|
|
err = d.DeleteStorageItem(m.ID, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = d.DeleteContractID(contract.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
siMap, err := d.GetStorageItems(contract.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k := range siMap {
|
|
err := d.DeleteStorageItem(contract.ID, []byte(k))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
m.markUpdated(hash)
|
|
return nil
|
|
}
|
|
|
|
func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(m.GetMinimumDeploymentFee(ic.DAO)))
|
|
}
|
|
|
|
// GetMinimumDeploymentFee returns the minimum required fee for contract deploy.
|
|
func (m *Management) GetMinimumDeploymentFee(dao dao.DAO) int64 {
|
|
return getIntWithKey(m.ID, dao, keyMinimumDeploymentFee)
|
|
}
|
|
|
|
func (m *Management) setMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toBigInt(args[0])
|
|
if value.Sign() < 0 {
|
|
panic("MinimumDeploymentFee cannot be negative")
|
|
}
|
|
if !m.NEO.checkCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
err := ic.DAO.PutStorageItem(m.ID, keyMinimumDeploymentFee, bigint.ToBytes(value))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, data stackitem.Item, isUpdate bool) {
|
|
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 2)
|
|
if md != nil {
|
|
err := contract.CallFromNative(ic, m.Hash, cs, manifest.MethodDeploy,
|
|
[]stackitem.Item{data, stackitem.NewBool(isUpdate)}, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func contractToStack(cs *state.Contract) stackitem.Item {
|
|
si, err := cs.ToStackItem()
|
|
if err != nil {
|
|
panic(fmt.Errorf("contract to stack item: %w", err))
|
|
}
|
|
return si
|
|
}
|
|
|
|
// Metadata implements Contract interface.
|
|
func (m *Management) Metadata() *interop.ContractMD {
|
|
return &m.ContractMD
|
|
}
|
|
|
|
// OnPersist implements Contract interface.
|
|
func (m *Management) OnPersist(ic *interop.Context) error {
|
|
for _, native := range ic.Natives {
|
|
md := native.Metadata()
|
|
history := md.UpdateHistory
|
|
if len(history) == 0 || history[0] != ic.Block.Index {
|
|
continue
|
|
}
|
|
|
|
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.contracts[md.Hash] = cs
|
|
m.mtx.Unlock()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitializeCache initializes contract 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 (m *Management) InitializeCache(d dao.DAO) error {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
var initErr error
|
|
d.Seek(m.ID, []byte{prefixContract}, func(_, v []byte) {
|
|
var cs state.Contract
|
|
r := io.NewBinReaderFromBuf(v)
|
|
cs.DecodeBinary(r)
|
|
if r.Err != nil {
|
|
initErr = r.Err
|
|
return
|
|
}
|
|
m.contracts[cs.Hash] = &cs
|
|
})
|
|
return initErr
|
|
}
|
|
|
|
// 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
|
|
}
|
|
newCs, err := m.getContractFromDAO(ic.DAO, h)
|
|
if err != nil {
|
|
// Contract was destroyed.
|
|
delete(m.contracts, h)
|
|
continue
|
|
}
|
|
m.contracts[h] = newCs
|
|
}
|
|
m.mtx.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Initialize implements Contract interface.
|
|
func (m *Management) Initialize(ic *interop.Context) error {
|
|
if err := setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee); err != nil {
|
|
return err
|
|
}
|
|
return setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1)
|
|
}
|
|
|
|
// PutContractState saves given contract state into given DAO.
|
|
func (m *Management) PutContractState(d dao.DAO, cs *state.Contract) error {
|
|
key := makeContractKey(cs.Hash)
|
|
if err := putSerializableToDAO(m.ID, d, key, cs); err != nil {
|
|
return err
|
|
}
|
|
m.markUpdated(cs.Hash)
|
|
if cs.UpdateCounter != 0 { // Update.
|
|
return nil
|
|
}
|
|
return d.PutContractID(cs.ID, cs.Hash)
|
|
}
|
|
|
|
func (m *Management) getNextContractID(d dao.DAO) (int32, error) {
|
|
si := d.GetStorageItem(m.ID, keyNextAvailableID)
|
|
if si == nil {
|
|
return 0, errors.New("nextAvailableID is not initialized")
|
|
}
|
|
id := bigint.FromBytes(si)
|
|
ret := int32(id.Int64())
|
|
id.Add(id, intOne)
|
|
si = bigint.ToPreallocatedBytes(id, si)
|
|
return ret, d.PutStorageItem(m.ID, keyNextAvailableID, si)
|
|
}
|
|
|
|
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) {
|
|
ne := state.NotificationEvent{
|
|
ScriptHash: m.Hash,
|
|
Name: name,
|
|
Item: stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}),
|
|
}
|
|
ic.Notifications = append(ic.Notifications, ne)
|
|
}
|
|
|
|
func checkScriptAndMethods(script []byte, methods []manifest.Method) error {
|
|
l := len(script)
|
|
offsets := bitfield.New(l)
|
|
for i := range methods {
|
|
if methods[i].Offset >= l {
|
|
return errors.New("out of bounds method offset")
|
|
}
|
|
offsets.Set(methods[i].Offset)
|
|
}
|
|
return vm.IsScriptCorrect(script, offsets)
|
|
}
|