Merge pull request #1557 from nspcc-dev/signature_collection/notary_contract

core: implement Notary contract
This commit is contained in:
Roman Khimov 2020-11-25 19:14:02 +03:00 committed by GitHub
commit ef15139179
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1269 additions and 284 deletions

View file

@ -17,6 +17,7 @@ ProtocolConfiguration:
- 127.0.0.1:20336
VerifyBlocks: true
VerifyTransactions: true
P2PSigExtensions: true
ApplicationConfiguration:
# LogPath could be set up in case you need stdout logs to some proper file.

View file

@ -171,7 +171,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
subCh: make(chan interface{}),
unsubCh: make(chan interface{}),
contracts: *native.NewContracts(),
contracts: *native.NewContracts(cfg.P2PSigExtensions),
}
if err := bc.init(); err != nil {
@ -705,9 +705,11 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
return err
}
if err := bc.contracts.Policy.OnPersistEnd(bc.dao); err != nil {
bc.lock.Unlock()
return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err)
}
if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil {
bc.lock.Unlock()
return err
}
bc.dao.MPT.Flush()
@ -1254,7 +1256,6 @@ func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error {
// Various errors that could be returned upon verification.
var (
ErrTxNotYetValid = errors.New("transaction is not yet valid")
ErrTxExpired = errors.New("transaction has expired")
ErrInsufficientFunds = errors.New("insufficient funds")
ErrTxSmallNetworkFee = errors.New("too small network fee")
@ -1281,6 +1282,13 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
return fmt.Errorf("%w: (%d > MaxTransactionSize %d)", ErrTxTooBig, size, transaction.MaxTransactionSize)
}
needNetworkFee := int64(size) * bc.FeePerByte()
if bc.P2PSigExtensionsEnabled() {
attrs := t.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
needNetworkFee += (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
}
}
netFee := t.NetworkFee - needNetworkFee
if netFee < 0 {
return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee)
@ -1329,12 +1337,9 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
switch attrType := tx.Attributes[i].Type; attrType {
case transaction.HighPriority:
h := bc.contracts.NEO.GetCommitteeAddress()
for i := range tx.Signers {
if tx.Signers[i].Account.Equals(h) {
return nil
}
if !tx.HasSigner(h) {
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute)
}
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute)
case transaction.OracleResponseT:
h, err := bc.contracts.Oracle.GetScriptHash(bc.dao)
if err != nil || h.Equals(util.Uint160{}) {
@ -1365,23 +1370,30 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
}
case transaction.NotValidBeforeT:
if !bc.config.P2PSigExtensions {
return errors.New("NotValidBefore attribute was found, but P2PSigExtensions are disabled")
return fmt.Errorf("%w: NotValidBefore attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
}
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore)
if height := bc.BlockHeight(); height < nvb.Height {
return fmt.Errorf("%w: NotValidBefore = %d, current height = %d", ErrTxNotYetValid, nvb.Height, height)
return fmt.Errorf("%w: transaction is not yet valid: NotValidBefore = %d, current height = %d", ErrInvalidAttribute, nvb.Height, height)
}
case transaction.ConflictsT:
if !bc.config.P2PSigExtensions {
return errors.New("Conflicts attribute was found, but P2PSigExtensions are disabled")
return fmt.Errorf("%w: Conflicts attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
}
conflicts := tx.Attributes[i].Value.(*transaction.Conflicts)
if err := bc.dao.HasTransaction(conflicts.Hash); errors.Is(err, dao.ErrAlreadyExists) {
return fmt.Errorf("conflicting transaction %s is already on chain", conflicts.Hash.StringLE())
return fmt.Errorf("%w: conflicting transaction %s is already on chain", ErrInvalidAttribute, conflicts.Hash.StringLE())
}
case transaction.NotaryAssistedT:
if !bc.config.P2PSigExtensions {
return fmt.Errorf("%w: NotaryAssisted attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
}
if !tx.HasSigner(bc.contracts.Notary.Hash) {
return fmt.Errorf("%w: NotaryAssisted attribute was found, but transaction is not signed by the Notary native contract", ErrInvalidAttribute)
}
default:
if !bc.config.ReservedAttributes && attrType >= transaction.ReservedLowerBound && attrType <= transaction.ReservedUpperBound {
return errors.New("attribute of reserved type was found, but ReservedAttributes are disabled")
return fmt.Errorf("%w: attribute of reserved type was found, but ReservedAttributes are disabled", ErrInvalidAttribute)
}
}
}
@ -1581,6 +1593,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160,
var isNative bool
var initMD *manifest.Method
verification := witness.VerificationScript
flags := smartcontract.NoneFlag
if len(verification) != 0 {
if witness.ScriptHash() != hash {
return ErrWitnessHashMismatch
@ -1601,10 +1614,11 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160,
offset = md.Offset
initMD = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
isNative = cs.ID < 0
flags = smartcontract.AllowStates
}
v := ic.VM
v.LoadScriptWithFlags(verification, smartcontract.NoneFlag)
v.LoadScriptWithFlags(verification, flags)
v.Jump(v.Context(), offset)
if isNative {
w := io.NewBufBinWriter()

View file

@ -286,7 +286,7 @@ func TestVerifyTx(t *testing.T) {
require.Equal(t, 1, len(aer))
require.Equal(t, aer[0].VMState, vm.HaltState)
res, err := invokeNativePolicyMethod(bc, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE())
res, err := invokeContractMethod(bc, 100000000, bc.contracts.Policy.Hash, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
@ -621,7 +621,7 @@ func TestVerifyTx(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("NotYetValid", func(t *testing.T) {
tx := getNVBTx(bc.blockHeight + 1)
require.True(t, errors.Is(bc.VerifyTx(tx), ErrTxNotYetValid))
require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute))
})
t.Run("positive", func(t *testing.T) {
tx := getNVBTx(bc.blockHeight)
@ -652,12 +652,12 @@ func TestVerifyTx(t *testing.T) {
return tx
}
t.Run("Disabled", func(t *testing.T) {
tx := getReservedTx(transaction.ReservedLowerBound + 2)
tx := getReservedTx(transaction.ReservedLowerBound + 3)
require.Error(t, bc.VerifyTx(tx))
})
t.Run("Enabled", func(t *testing.T) {
bc.config.ReservedAttributes = true
tx := getReservedTx(transaction.ReservedLowerBound + 2)
tx := getReservedTx(transaction.ReservedLowerBound + 3)
require.NoError(t, bc.VerifyTx(tx))
})
})
@ -719,6 +719,207 @@ func TestVerifyTx(t *testing.T) {
})
})
})
t.Run("NotaryAssisted", func(t *testing.T) {
notary, err := wallet.NewAccount()
require.NoError(t, err)
txSetNotary := transaction.New(netmode.UnitTestNet, []byte{}, 0)
setSigner(txSetNotary, testchain.CommitteeScriptHash())
txSetNotary.Scripts = []transaction.Witness{{
InvocationScript: testchain.SignCommittee(txSetNotary.GetSignedPart()),
VerificationScript: testchain.CommitteeVerificationScript(),
}}
bl := block.New(netmode.UnitTestNet, false)
bl.Index = bc.BlockHeight() + 1
ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetNotary)
ic.SpawnVM()
ic.VM.LoadScript([]byte{byte(opcode.RET)})
require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{notary.PrivateKey().PublicKey()}))
require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO))
_, err = ic.DAO.Persist()
require.NoError(t, err)
getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction {
tx := bc.newTestTx(h, testScript)
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{
NKeys: signaturesCount,
}})
tx.NetworkFee += serviceFee // additional fee for NotaryAssisted attribute
tx.NetworkFee += 4_000_000 // multisig check
tx.Signers = []transaction.Signer{{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
rawScript := testchain.CommitteeVerificationScript()
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(rawScript)
tx.NetworkFee += netFee
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: rawScript,
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().Sign(data)...),
},
}
return tx
}
t.Run("Disabled", func(t *testing.T) {
bc.config.P2PSigExtensions = false
tx := getNotaryAssistedTx(0, 0)
require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute))
})
t.Run("Enabled, insufficient network fee", func(t *testing.T) {
bc.config.P2PSigExtensions = true
tx := getNotaryAssistedTx(1, 0)
require.Error(t, bc.VerifyTx(tx))
})
t.Run("Test verify", func(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("no NotaryAssisted attribute", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Attributes = []transaction.Attribute{}
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().Sign(data)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("no deposit", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().Sign(data)...),
},
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: testchain.CommitteeVerificationScript(),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary signer scope", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.CalledByEntry,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().Sign(data)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("not signed by Notary", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: testchain.CommitteeVerificationScript(),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary node witness", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.None,
},
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
acc, err := keys.NewPrivateKey()
require.NoError(t, err)
tx.Scripts = []transaction.Witness{
{
InvocationScript: testchain.SignCommittee(data),
VerificationScript: testchain.CommitteeVerificationScript(),
},
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.Sign(data)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("missing payer", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().Sign(data)...),
},
}
require.Error(t, bc.VerifyTx(tx))
})
t.Run("positive", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
require.NoError(t, bc.VerifyTx(tx))
})
})
})
})
}

View file

@ -18,14 +18,18 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/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"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
@ -364,9 +368,9 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
require.NoError(t, bc.AddBlock(b))
}
func newNEP17Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction {
func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount, nil)
emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount, additionalArgs)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
script := w.Bytes()
@ -411,3 +415,56 @@ func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.A
tx.NetworkFee += int64(size) * bc.FeePerByte()
return nil
}
func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, hash, method, args...)
if w.Err != nil {
return nil, w.Err
}
script := w.Bytes()
tx := transaction.New(chain.GetConfig().Magic, script, sysfee)
tx.ValidUntilBlock = chain.blockHeight + 1
addSigners(tx)
err := testchain.SignTx(chain, tx)
if err != nil {
return nil, err
}
b := chain.newBlock(tx)
err = chain.AddBlock(b)
if err != nil {
return nil, err
}
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
if err != nil {
return nil, err
}
return &res[0], nil
}
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
transferTx.SystemFee = 100000000
transferTx.ValidUntilBlock = chain.BlockHeight() + 1
addSigners(transferTx)
require.NoError(t, testchain.SignTx(chain, transferTx))
b := chain.newBlock(transferTx)
require.NoError(t, chain.AddBlock(b))
return transferTx
}
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
require.Equal(t, vm.HaltState, result.VMState)
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected, result.Stack[0])
}
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
require.Equal(t, vm.FaultState, result.VMState)
}
func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected int) {
balance := chain.GetNEP17Balances(addr).Trackers[chain.contracts.GAS.ContractID]
require.Equal(t, int64(expected), balance.Balance.Int64())
}

View file

@ -52,12 +52,12 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
return errors.New("disallowed method call")
}
}
return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty)
return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty, nil)
}
// CallExInternal calls a contract with flags and can't be invoked directly by user.
func CallExInternal(ic *interop.Context, cs *state.Contract,
name string, args []stackitem.Item, f smartcontract.CallFlag, checkReturn vm.CheckReturnState) error {
name string, args []stackitem.Item, f smartcontract.CallFlag, checkReturn vm.CheckReturnState, callback func(ctx *vm.Context)) error {
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return fmt.Errorf("method '%s' not found", name)
@ -88,6 +88,7 @@ func CallExInternal(ic *interop.Context, cs *state.Contract,
ic.VM.Jump(ic.VM.Context(), md.Offset)
}
ic.VM.Context().CheckReturn = checkReturn
ic.VM.Context().Callback = callback
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
if md != nil {

View file

@ -195,7 +195,7 @@ func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error {
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy)
if md != nil {
return contract.CallExInternal(ic, cs, manifest.MethodDeploy,
[]stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty)
[]stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty, nil)
}
return nil
}

View file

@ -911,7 +911,7 @@ func TestContractCreateDeploy(t *testing.T) {
require.NoError(t, ic.VM.Run())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty)
err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty, nil)
require.NoError(t, err)
require.NoError(t, v.Run())
require.Equal(t, "create", v.Estack().Pop().String())
@ -932,7 +932,7 @@ func TestContractCreateDeploy(t *testing.T) {
require.NoError(t, v.Run())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty)
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty, nil)
require.NoError(t, err)
require.NoError(t, v.Run())
require.Equal(t, "update", v.Estack().Pop().String())

View file

@ -12,6 +12,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
// reservedContractID represents the upper bound of the reserved IDs for native contracts.
const reservedContractID = -100
// Contracts is a set of registered native contracts.
type Contracts struct {
NEO *NEO
@ -19,6 +22,7 @@ type Contracts struct {
Policy *Policy
Oracle *Oracle
Designate *Designate
Notary *Notary
Contracts []interop.Contract
// persistScript is vm script which executes "onPersist" method of every native contract.
persistScript []byte
@ -47,9 +51,9 @@ func (cs *Contracts) ByName(name string) interop.Contract {
return nil
}
// NewContracts returns new set of native contracts with new GAS, NEO and Policy
// contracts.
func NewContracts() *Contracts {
// NewContracts returns new set of native contracts with new GAS, NEO, Policy, Oracle,
// Designate and (optional) Notary contracts.
func NewContracts(p2pSigExtensionsEnabled bool) *Contracts {
cs := new(Contracts)
gas := newGAS()
@ -72,12 +76,20 @@ func NewContracts() *Contracts {
cs.Oracle = oracle
cs.Contracts = append(cs.Contracts, oracle)
desig := newDesignate()
desig := newDesignate(p2pSigExtensionsEnabled)
desig.NEO = neo
cs.Designate = desig
cs.Oracle.Desig = desig
cs.Contracts = append(cs.Contracts, desig)
if p2pSigExtensionsEnabled {
notary := newNotary()
notary.GAS = gas
notary.Desig = desig
cs.Notary = notary
cs.Contracts = append(cs.Contracts, notary)
}
return cs
}
@ -114,7 +126,7 @@ func (cs *Contracts) GetPostPersistScript() []byte {
md := cs.Contracts[i].Metadata()
// Not every contract is persisted:
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
if md.ContractID == policyContractID || md.ContractID == gasContractID || md.ContractID == designateContractID {
if md.ContractID == policyContractID || md.ContractID == gasContractID || md.ContractID == designateContractID || md.ContractID == notaryContractID {
continue
}
emit.Int(w.BinWriter, 0)
@ -133,3 +145,10 @@ func postPersistBase(ic *interop.Context) error {
}
return nil
}
func onPersistBase(ic *interop.Context) error {
if ic.Trigger != trigger.OnPersist {
return errors.New("onPersist must be trigered by system")
}
return nil
}

View file

@ -27,6 +27,9 @@ type Designate struct {
rolesChangedFlag atomic.Value
oracles atomic.Value
// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
p2pSigExtensionsEnabled bool
}
type oraclesData struct {
@ -50,6 +53,7 @@ type Role byte
const (
RoleStateValidator Role = 4
RoleOracle Role = 8
RoleP2PNotary Role = 128
)
// Various errors.
@ -62,13 +66,14 @@ var (
ErrNoBlock = errors.New("no persisting block in the context")
)
func isValidRole(r Role) bool {
return r == RoleOracle || r == RoleStateValidator
func (s *Designate) isValidRole(r Role) bool {
return r == RoleOracle || r == RoleStateValidator || (s.p2pSigExtensionsEnabled && r == RoleP2PNotary)
}
func newDesignate() *Designate {
func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
s := &Designate{ContractMD: *interop.NewContractMD(designateName)}
s.ContractID = designateContractID
s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled
desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType,
manifest.NewParameter("role", smartcontract.IntegerType),
@ -82,9 +87,13 @@ func newDesignate() *Designate {
md = newMethodAndPrice(s.designateAsRole, 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
desc = newDescriptor("name", smartcontract.StringType)
md = newMethodAndPrice(nameMethod(designateName), 0, smartcontract.NoneFlag)
s.AddMethod(md, desc, true)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
return s
}
@ -121,7 +130,7 @@ func (s *Designate) Metadata() *interop.ContractMD {
}
func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
r, ok := getRole(args[0])
r, ok := s.getRole(args[0])
if !ok {
panic(ErrInvalidRole)
}
@ -154,7 +163,7 @@ func oracleHashFromNodes(nodes keys.PublicKeys) util.Uint160 {
}
func (s *Designate) getLastDesignatedHash(d dao.DAO, r Role) (util.Uint160, error) {
if !isValidRole(r) {
if !s.isValidRole(r) {
return util.Uint160{}, ErrInvalidRole
}
if r == RoleOracle && !s.rolesChanged() {
@ -174,7 +183,7 @@ func (s *Designate) getLastDesignatedHash(d dao.DAO, r Role) (util.Uint160, erro
// GetDesignatedByRole returns nodes for role r.
func (s *Designate) GetDesignatedByRole(d dao.DAO, r Role, index uint32) (keys.PublicKeys, uint32, error) {
if !isValidRole(r) {
if !s.isValidRole(r) {
return nil, 0, ErrInvalidRole
}
if r == RoleOracle && !s.rolesChanged() {
@ -214,7 +223,7 @@ func (s *Designate) GetDesignatedByRole(d dao.DAO, r Role, index uint32) (keys.P
}
func (s *Designate) designateAsRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
r, ok := getRole(args[0])
r, ok := s.getRole(args[0])
if !ok {
panic(ErrInvalidRole)
}
@ -239,7 +248,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r Role, pubs keys.Publi
if length > maxNodeCount {
return ErrLargeNodeList
}
if !isValidRole(r) {
if !s.isValidRole(r) {
return ErrInvalidRole
}
h := s.NEO.GetCommitteeAddress()
@ -263,7 +272,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r Role, pubs keys.Publi
return ic.DAO.PutStorageItem(s.ContractID, key, si)
}
func getRole(item stackitem.Item) (Role, bool) {
func (s *Designate) getRole(item stackitem.Item) (Role, bool) {
bi, err := item.TryInteger()
if err != nil {
return 0, false
@ -272,5 +281,5 @@ func getRole(item stackitem.Item) (Role, bool) {
return 0, false
}
u := bi.Uint64()
return Role(u), u <= math.MaxUint8 && isValidRole(Role(u))
return Role(u), u <= math.MaxUint8 && s.isValidRole(Role(u))
}

View file

@ -31,7 +31,7 @@ func newGAS() *GAS {
nep17.symbol = "gas"
nep17.decimals = 8
nep17.factor = GASFactor
nep17.onPersist = chainOnPersist(nep17.OnPersist, g.OnPersist)
nep17.onPersist = chainOnPersist(onPersistBase, g.OnPersist)
nep17.incBalance = g.increaseBalance
nep17.ContractID = gasContractID

View file

@ -97,7 +97,7 @@ func newNEO() *NEO {
nep17.symbol = "neo"
nep17.decimals = 0
nep17.factor = 1
nep17.onPersist = chainOnPersist(nep17.OnPersist, n.OnPersist)
nep17.onPersist = chainOnPersist(onPersistBase, n.OnPersist)
nep17.postPersist = chainOnPersist(nep17.postPersist, n.PostPersist)
nep17.incBalance = n.increaseBalance
nep17.ContractID = neoContractID

View file

@ -3,6 +3,7 @@ package native
import (
"errors"
"fmt"
"math"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
@ -13,7 +14,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -54,12 +54,8 @@ func newNEP17Native(name string) *nep17TokenNative {
n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name)}
n.Manifest.SupportedStandards = []string{manifest.NEP17StandardName}
desc := newDescriptor("name", smartcontract.StringType)
md := newMethodAndPrice(nameMethod(name), 0, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("symbol", smartcontract.StringType)
md = newMethodAndPrice(n.Symbol, 0, smartcontract.NoneFlag)
desc := newDescriptor("symbol", smartcontract.StringType)
md := newMethodAndPrice(n.Symbol, 0, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("decimals", smartcontract.IntegerType)
@ -75,24 +71,26 @@ func newNEP17Native(name string) *nep17TokenNative {
md = newMethodAndPrice(n.balanceOf, 1000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("transfer", smartcontract.BoolType,
transferParams := []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType),
}
desc = newDescriptor("transfer", smartcontract.BoolType,
append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
)
md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddEvent("Transfer", desc.Parameters...)
n.AddEvent("Transfer", transferParams...)
return n
}
@ -160,7 +158,7 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint
stackitem.NewBigInteger(amount),
data,
}
if err := contract.CallExInternal(ic, cs, manifest.MethodOnPayment, args, smartcontract.All, vm.EnsureIsEmpty); err != nil {
if err := contract.CallExInternal(ic, cs, manifest.MethodOnPayment, args, smartcontract.All, vm.EnsureIsEmpty, nil); err != nil {
panic(err)
}
}
@ -287,13 +285,6 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
}
}
func (c *nep17TokenNative) OnPersist(ic *interop.Context) error {
if ic.Trigger != trigger.OnPersist {
return errors.New("onPersist must be triggerred by system")
}
return nil
}
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
return &manifest.Method{
Name: name,
@ -330,6 +321,18 @@ func toUint160(s stackitem.Item) util.Uint160 {
return u
}
func toUint32(s stackitem.Item) uint32 {
bigInt := toBigInt(s)
if !bigInt.IsInt64() {
panic("bigint is not an int64")
}
int64Value := bigInt.Int64()
if int64Value < 0 || int64Value > math.MaxUint32 {
panic("bigint does not fit into uint32")
}
return uint32(int64Value)
}
func getOnPersistWrapper(f func(ic *interop.Context) error) interop.Method {
return func(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
err := f(ic)

357
pkg/core/native/notary.go Normal file
View file

@ -0,0 +1,357 @@
package native
import (
"errors"
"fmt"
"math"
"math/big"
"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/runtime"
"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/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Notary represents Notary native contract.
type Notary struct {
interop.ContractMD
GAS *GAS
Desig *Designate
}
const (
notaryName = "Notary"
notaryContractID = reservedContractID - 1
// prefixDeposit is a prefix for storing Notary deposits.
prefixDeposit = 1
)
// newNotary returns Notary native contract.
func newNotary() *Notary {
n := &Notary{ContractMD: *interop.NewContractMD(notaryName)}
n.ContractID = notaryContractID
desc := newDescriptor("onPayment", smartcontract.VoidType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType))
md := newMethodAndPrice(n.onPayment, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("lockDepositUntil", smartcontract.BoolType,
manifest.NewParameter("address", smartcontract.Hash160Type),
manifest.NewParameter("till", smartcontract.IntegerType))
md = newMethodAndPrice(n.lockDepositUntil, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("withdraw", smartcontract.BoolType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type))
md = newMethodAndPrice(n.withdraw, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("expirationOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.expirationOf, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
desc = newDescriptor("verify", smartcontract.BoolType,
manifest.NewParameter("signature", smartcontract.SignatureType))
md = newMethodAndPrice(n.verify, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, false)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
return n
}
// Metadata implements Contract interface.
func (n *Notary) Metadata() *interop.ContractMD {
return &n.ContractMD
}
// Initialize initializes Notary native contract and implements Contract interface.
func (n *Notary) Initialize(ic *interop.Context) error {
return nil
}
// OnPersist implements Contract interface.
func (n *Notary) OnPersist(ic *interop.Context) error {
var (
nFees int64
notaries keys.PublicKeys
err error
)
for _, tx := range ic.Block.Transactions {
if tx.HasAttribute(transaction.NotaryAssistedT) {
if notaries == nil {
notaries, err = n.GetNotaryNodes(ic.DAO)
if err != nil {
return fmt.Errorf("failed to get notary nodes: %w", err)
}
}
nKeys := tx.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys
nFees += int64(nKeys) + 1
if tx.Sender() == n.Hash {
payer := tx.Signers[1]
balance := n.getDepositFor(ic.DAO, payer.Account)
balance.Amount.Sub(balance.Amount, big.NewInt(tx.SystemFee+tx.NetworkFee))
if balance.Amount.Sign() == 0 {
err := n.removeDepositFor(ic.DAO, payer.Account)
if err != nil {
return fmt.Errorf("failed to remove an empty deposit for %s from storage: %w", payer.Account.StringBE(), err)
}
} else {
err := n.putDepositFor(ic.DAO, balance, payer.Account)
if err != nil {
return fmt.Errorf("failed to update deposit for %s: %w", payer.Account.StringBE(), err)
}
}
}
}
}
if nFees == 0 {
return nil
}
singleReward := calculateNotaryReward(nFees, len(notaries))
for _, notary := range notaries {
n.GAS.mint(ic, notary.GetScriptHash(), singleReward)
}
return nil
}
// onPayment records deposited amount as belonging to "from" address with a lock
// till the specified chain's height.
func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if h := ic.VM.GetCallingScriptHash(); h != n.GAS.Hash {
panic(fmt.Errorf("only GAS can be accepted for deposit, got %s", h.StringBE()))
}
from := toUint160(args[0])
to := from
amount := toBigInt(args[1])
data, ok := args[2].(*stackitem.Array)
if !ok || len(data.Value().([]stackitem.Item)) != 2 {
panic(errors.New("`data` parameter should be an array of 2 elements"))
}
additionalParams := data.Value().([]stackitem.Item)
if !additionalParams[0].Equals(stackitem.Null{}) {
to = toUint160(additionalParams[0])
}
till := toUint32(additionalParams[1])
currentHeight := ic.Chain.BlockHeight()
if till < currentHeight {
panic(fmt.Errorf("`till` shouldn't be less then the chain's height %d", currentHeight))
}
deposit := n.getDepositFor(ic.DAO, to)
if deposit == nil {
if amount.Cmp(big.NewInt(2*transaction.NotaryServiceFeePerKey)) < 0 {
panic(fmt.Errorf("first deposit can not be less then %d, got %d", 2*transaction.NotaryServiceFeePerKey, amount.Int64()))
}
deposit = &state.Deposit{
Amount: new(big.Int),
}
} else {
if till < deposit.Till {
panic(fmt.Errorf("`till` shouldn't be less then the previous value %d", deposit.Till))
}
}
deposit.Amount.Add(deposit.Amount, amount)
deposit.Till = till
if err := n.putDepositFor(ic.DAO, deposit, to); err != nil {
panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", from.StringBE(), err))
}
return stackitem.Null{}
}
// lockDepositUntil updates the chain's height until which deposit is locked.
func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) stackitem.Item {
addr := toUint160(args[0])
ok, err := runtime.CheckHashedWitness(ic, addr)
if err != nil {
panic(fmt.Errorf("failed to check witness for %s: %w", addr.StringBE(), err))
}
if !ok {
return stackitem.NewBool(false)
}
till := toUint32(args[1])
if till < ic.Chain.BlockHeight() {
return stackitem.NewBool(false)
}
deposit := n.getDepositFor(ic.DAO, addr)
if deposit == nil {
return stackitem.NewBool(false)
}
if till < deposit.Till {
return stackitem.NewBool(false)
}
deposit.Till = till
err = n.putDepositFor(ic.DAO, deposit, addr)
if err != nil {
panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", addr.StringBE(), err))
}
return stackitem.NewBool(true)
}
// withdraw sends all deposited GAS for "from" address to "to" address.
func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
from := toUint160(args[0])
ok, err := runtime.CheckHashedWitness(ic, from)
if err != nil {
panic(fmt.Errorf("failed to check witness for %s: %w", from.StringBE(), err))
}
if !ok {
return stackitem.NewBool(false)
}
to := from
if !args[1].Equals(stackitem.Null{}) {
to = toUint160(args[1])
}
deposit := n.getDepositFor(ic.DAO, from)
if deposit == nil {
return stackitem.NewBool(false)
}
if ic.Chain.BlockHeight() < deposit.Till {
return stackitem.NewBool(false)
}
cs, err := ic.DAO.GetContractState(n.GAS.Hash)
if err != nil {
panic(fmt.Errorf("failed to get GAS contract state: %w", err))
}
transferArgs := []stackitem.Item{stackitem.NewByteArray(n.Hash.BytesBE()), stackitem.NewByteArray(to.BytesBE()), stackitem.NewBigInteger(deposit.Amount), stackitem.Null{}}
err = contract.CallExInternal(ic, cs, "transfer", transferArgs, smartcontract.All, vm.EnsureIsEmpty, func(ctx *vm.Context) { // we need EnsureIsEmpty because there's a callback popping result from the stack
isTransferOk := ic.VM.Estack().Pop().Bool()
if !isTransferOk {
panic("failed to transfer GAS from Notary account")
}
})
if err != nil {
panic(fmt.Errorf("failed to transfer GAS from Notary account: %w", err))
}
if err := n.removeDepositFor(ic.DAO, from); err != nil {
panic(fmt.Errorf("failed to remove withdrawn deposit for %s from the storage: %w", from.StringBE(), err))
}
return stackitem.NewBool(true)
}
// balanceOf returns deposited GAS amount for specified address.
func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
acc := toUint160(args[0])
deposit := n.getDepositFor(ic.DAO, acc)
if deposit == nil {
return stackitem.NewBigInteger(big.NewInt(0))
}
return stackitem.NewBigInteger(deposit.Amount)
}
// expirationOf Returns deposit lock height for specified address.
func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
acc := toUint160(args[0])
deposit := n.getDepositFor(ic.DAO, acc)
if deposit == nil {
return stackitem.Make(0)
}
return stackitem.Make(deposit.Till)
}
// verify checks whether the transaction was signed by one of the notaries.
func (n *Notary) verify(ic *interop.Context, args []stackitem.Item) stackitem.Item {
sig, err := args[0].TryBytes()
if err != nil {
panic(fmt.Errorf("failed to get signature bytes: %w", err))
}
tx := ic.Tx
if len(tx.GetAttributes(transaction.NotaryAssistedT)) == 0 {
return stackitem.NewBool(false)
}
for _, signer := range tx.Signers {
if signer.Account == n.Hash {
if signer.Scopes != transaction.None {
return stackitem.NewBool(false)
}
}
}
if tx.Sender() == n.Hash {
if len(tx.Signers) != 2 {
return stackitem.NewBool(false)
}
payer := tx.Signers[1].Account
balance := n.getDepositFor(ic.DAO, payer)
if balance == nil || balance.Amount.Cmp(big.NewInt(tx.NetworkFee+tx.SystemFee)) < 0 {
return stackitem.NewBool(false)
}
}
notaries, err := n.GetNotaryNodes(ic.DAO)
if err != nil {
panic(fmt.Errorf("failed to get notary nodes: %w", err))
}
hash := tx.GetSignedHash().BytesBE()
var verified bool
for _, n := range notaries {
if n.Verify(sig, hash) {
verified = true
break
}
}
return stackitem.NewBool(verified)
}
// GetNotaryNodes returns public keys of notary nodes.
func (n *Notary) GetNotaryNodes(d dao.DAO) (keys.PublicKeys, error) {
nodes, _, err := n.Desig.GetDesignatedByRole(d, RoleP2PNotary, math.MaxUint32)
return nodes, err
}
// getDepositFor returns state.Deposit for the account specified. It returns nil in case if
// deposit is not found in storage and panics in case of any other error.
func (n *Notary) getDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit {
key := append([]byte{prefixDeposit}, acc.BytesBE()...)
deposit := new(state.Deposit)
err := getSerializableFromDAO(n.ContractID, dao, key, deposit)
if err == nil {
return deposit
}
if err == storage.ErrKeyNotFound {
return nil
}
panic(fmt.Errorf("failed to get deposit for %s from storage: %w", acc.StringBE(), err))
}
// putDepositFor puts deposit on the balance of the specified account in the storage.
func (n *Notary) putDepositFor(dao dao.DAO, deposit *state.Deposit, acc util.Uint160) error {
key := append([]byte{prefixDeposit}, acc.BytesBE()...)
return putSerializableToDAO(n.ContractID, dao, key, deposit)
}
// removeDepositFor removes deposit from the storage.
func (n *Notary) removeDepositFor(dao dao.DAO, acc util.Uint160) error {
key := append([]byte{prefixDeposit}, acc.BytesBE()...)
return dao.DeleteStorageItem(n.ContractID, key)
}
// calculateNotaryReward calculates the reward for a single notary node based on FEE's count and Notary nodes count.
func calculateNotaryReward(nFees int64, notariesCount int) *big.Int {
return big.NewInt(nFees * transaction.NotaryServiceFeePerKey / int64(notariesCount))
}

View file

@ -113,10 +113,6 @@ func newOracle() *Oracle {
md := newMethodAndPrice(o.request, oracleRequestPrice, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
desc = newDescriptor("name", smartcontract.StringType)
md = newMethodAndPrice(nameMethod(oracleName), 0, smartcontract.NoneFlag)
o.AddMethod(md, desc, true)
desc = newDescriptor("finish", smartcontract.VoidType)
md = newMethodAndPrice(o.finish, 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
@ -137,6 +133,10 @@ func newOracle() *Oracle {
md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
o.AddEvent("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType),
manifest.NewParameter("RequestContract", smartcontract.Hash160Type),
manifest.NewParameter("Url", smartcontract.StringType),

View file

@ -126,10 +126,6 @@ func newPolicy() *Policy {
md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
desc = newDescriptor("name", smartcontract.StringType)
md = newMethodAndPrice(nameMethod(policyName), 0, smartcontract.NoneFlag)
p.AddMethod(md, desc, true)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)

View file

@ -2,10 +2,9 @@ package native
import (
"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/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error {
@ -18,8 +17,13 @@ func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializabl
return r.Err
}
func nameMethod(name string) interop.Method {
return func(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewByteArray([]byte(name))
func putSerializableToDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error {
w := io.NewBufBinWriter()
item.EncodeBinary(w.BinWriter)
if w.Err != nil {
return w.Err
}
return d.PutStorageItem(id, key, &state.StorageItem{
Value: w.Bytes(),
})
}

View file

@ -4,7 +4,6 @@ import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -12,14 +11,11 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
@ -133,7 +129,7 @@ func (tn *testNative) call(ic *interop.Context, args []stackitem.Item, retState
if err != nil {
panic(err)
}
err = contract.CallExInternal(ic, cs, string(bs), args[2].Value().([]stackitem.Item), smartcontract.All, retState)
err = contract.CallExInternal(ic, cs, string(bs), args[2].Value().([]stackitem.Item), smartcontract.All, retState, nil)
if err != nil {
panic(err)
}
@ -157,44 +153,24 @@ func TestNativeContract_Invoke(t *testing.T) {
})
require.NoError(t, err)
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28))
script := w.Bytes()
// System.Contract.Call + "sum" itself + opcodes for pushing arguments (PACK is 15000)
tx := transaction.New(chain.GetConfig().Magic, script, testSumPrice*2+18000)
validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil
addSigners(tx)
require.NoError(t, testchain.SignTx(chain, tx))
// Enough for Call and other opcodes, but not enough for "sum" call.
tx2 := transaction.New(chain.GetConfig().Magic, script, testSumPrice*2+8000)
tx2.ValidUntilBlock = chain.blockHeight + 1
addSigners(tx2)
require.NoError(t, testchain.SignTx(chain, tx2))
b := chain.newBlock(tx, tx2)
require.NoError(t, chain.AddBlock(b))
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
res, err := invokeContractMethod(chain, testSumPrice*2+18000, tn.Metadata().Hash, "sum", int64(14), int64(28))
require.NoError(t, err)
require.Equal(t, 1, len(res))
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 1, len(res[0].Stack))
require.Equal(t, big.NewInt(42), res[0].Stack[0].Value())
res, err = chain.GetAppExecResults(tx2.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(res))
require.Equal(t, vm.FaultState, res[0].VMState)
checkResult(t, res, stackitem.Make(42))
require.NoError(t, chain.persist())
select {
case index := <-tn.blocks:
require.Equal(t, chain.blockHeight, index)
default:
require.Fail(t, "onPersist wasn't called")
}
// Enough for Call and other opcodes, but not enough for "sum" call.
res, err = invokeContractMethod(chain, testSumPrice*2+8000, tn.Metadata().Hash, "sum", int64(14), int64(28))
require.NoError(t, err)
checkFAULTState(t, res)
}
func TestNativeContract_InvokeInternal(t *testing.T) {
@ -254,50 +230,8 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
require.NoError(t, chain.dao.PutContractState(cs))
t.Run("non-native, no return", func(t *testing.T) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "callOtherContractNoReturn",
cs.ScriptHash(), "justReturn", []interface{}{})
require.NoError(t, w.Err)
script := w.Bytes()
tx := transaction.New(chain.GetConfig().Magic, script, testSumPrice*4+10000)
validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil
addSigners(tx)
require.NoError(t, testchain.SignTx(chain, tx))
b := chain.newBlock(tx)
require.NoError(t, chain.AddBlock(b))
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.ScriptHash(), "justReturn", []interface{}{})
require.NoError(t, err)
require.Equal(t, 1, len(res))
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 1, len(res[0].Stack))
require.Equal(t, stackitem.Null{}, res[0].Stack[0]) // simple call is done with EnsureNotEmpty
checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty
})
}
func TestAllContractsHaveName(t *testing.T) {
bc := newTestChain(t)
defer bc.Close()
for _, c := range bc.contracts.Contracts {
name := c.Metadata().Name
t.Run(name, func(t *testing.T) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, c.Metadata().Hash, "name")
require.NoError(t, w.Err)
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 1015570)
tx.ValidUntilBlock = bc.blockHeight + 1
addSigners(tx)
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
aers, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aers))
require.Len(t, aers[0].Stack, 1)
require.Equal(t, []byte(name), aers[0].Stack[0].Value())
})
}
}

View file

@ -64,34 +64,17 @@ func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r native.Role, nodes
}
func (bc *Blockchain) getNodesByRole(t *testing.T, ok bool, r native.Role, index uint32, resLen int) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.Designate.Hash, "getDesignatedByRole", int64(r), int64(index))
require.NoError(t, w.Err)
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 0)
tx.NetworkFee = 10_000_000
tx.SystemFee = 10_000_000
tx.ValidUntilBlock = 100
tx.Signers = []transaction.Signer{
{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
},
}
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
res, err := invokeContractMethod(bc, 10_000_000, bc.contracts.Designate.Hash, "getDesignatedByRole", int64(r), int64(index))
require.NoError(t, err)
require.Equal(t, 1, len(aer))
if ok {
require.Equal(t, vm.HaltState, aer[0].VMState)
require.Equal(t, 1, len(aer[0].Stack))
arrItem := aer[0].Stack[0]
require.Equal(t, vm.HaltState, res.VMState)
require.Equal(t, 1, len(res.Stack))
arrItem := res.Stack[0]
require.Equal(t, stackitem.ArrayT, arrItem.Type())
arr := arrItem.(*stackitem.Array)
require.Equal(t, resLen, arr.Len())
} else {
require.Equal(t, vm.FaultState, aer[0].VMState)
checkFAULTState(t, res)
}
}
@ -163,7 +146,7 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
require.Equal(t, 0, len(pubs))
require.Equal(t, uint32(0), index)
// Set another role.
// Set StateValidator role.
_, err = keys.NewPrivateKey()
require.NoError(t, err)
pub1 := priv.PublicKey()
@ -180,4 +163,19 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
require.NoError(t, err)
require.Equal(t, keys.PublicKeys{pub1}, pubs)
require.Equal(t, bl.Index+1, index)
// Set P2PNotary role.
pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleP2PNotary, 255)
require.NoError(t, err)
require.Equal(t, 0, len(pubs))
require.Equal(t, uint32(0), index)
err = des.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{pub1})
require.NoError(t, err)
require.NoError(t, des.OnPersistEnd(ic.DAO))
pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleP2PNotary, 255)
require.NoError(t, err)
require.Equal(t, keys.PublicKeys{pub1}, pubs)
require.Equal(t, bl.Index+1, index)
}

View file

@ -313,14 +313,7 @@ func TestNEO_TransferOnPayment(t *testing.T) {
require.NoError(t, bc.dao.PutContractState(cs))
const amount = 2
tx := newNEP17Transfer(bc.contracts.NEO.Hash, neoOwner, cs.ScriptHash(), amount)
tx.SystemFee += 1_000_000
tx.NetworkFee = 10_000_000
tx.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(tx)
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
tx := transferTokenFromMultisigAccount(t, bc, cs.ScriptHash(), bc.contracts.NEO.Hash, amount)
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState)

View file

@ -0,0 +1,298 @@
package core
import (
"testing"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
func TestNotaryContractPipeline(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
notaryHash := chain.contracts.Notary.Hash
gasHash := chain.contracts.GAS.Hash
depositLock := 30
// check Notary contract has no GAS on the account
checkBalanceOf(t, chain, notaryHash, 0)
// `balanceOf`: check multisig account has no GAS on deposit
balance, err := invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(0))
// `expirationOf`: should fail to get deposit which does not exist
till, err := invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(0))
// `lockDepositUntil`: should fail because there's no deposit
lockDepositUntilRes, err := invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+1))
require.NoError(t, err)
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
// `onPayment`: bad token
transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, chain.contracts.NEO.Hash, 1, nil, int64(depositLock))
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
checkFAULTState(t, &res[0])
// `onPayment`: insufficient first deposit
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey-1, nil, int64(depositLock))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
checkFAULTState(t, &res[0])
// `onPayment`: invalid `data` (missing `till` parameter)
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey-1, nil)
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
checkFAULTState(t, &res[0])
// `onPayment`: good
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey, nil, int64(depositLock))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
checkBalanceOf(t, chain, notaryHash, 2*transaction.NotaryServiceFeePerKey)
// `expirationOf`: check `till` was set
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock))
// `balanceOf`: check deposited amount for the multisig account
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(2*transaction.NotaryServiceFeePerKey))
// `onPayment`: good second deposit and explicit `to` paramenter
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, transaction.NotaryServiceFeePerKey, testchain.MultisigScriptHash(), int64(depositLock+1))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
// `balanceOf`: check deposited amount for the multisig account
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
// `expirationOf`: check `till` is updated.
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+1))
// `onPayment`: empty payment, should fail because `till` less then the previous one
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(depositLock))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
checkFAULTState(t, &res[0])
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+1))
// `onPayment`: empty payment, should fail because `till` less then the chain height
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(1))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
checkFAULTState(t, &res[0])
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+1))
// `onPayment`: empty payment, should successfully update `till`
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(depositLock+2))
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+2))
// `lockDepositUntil`: bad witness
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", util.Uint160{1, 2, 3}, int64(depositLock+5))
require.NoError(t, err)
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+2))
// `lockDepositUntil`: bad `till` (less then the previous one)
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+1))
require.NoError(t, err)
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+2))
// `lockDepositUntil`: bad `till` (less then the chain's height)
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(1))
require.NoError(t, err)
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+2))
// `lockDepositUntil`: good `till`
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+3))
require.NoError(t, err)
checkResult(t, lockDepositUntilRes, stackitem.NewBool(true))
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, till, stackitem.Make(depositLock+3))
// transfer 1 GAS to the new account for the next test
acc, _ := wallet.NewAccount()
transferTx = transferTokenFromMultisigAccount(t, chain, acc.PrivateKey().PublicKey().GetScriptHash(), gasHash, 100000000)
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
// `withdraw`: bad witness
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, notaryHash, "withdraw", testchain.MultisigScriptHash(), acc.PrivateKey().PublicKey().GetScriptHash())
require.NoError(t, w.Err)
script := w.Bytes()
withdrawTx := transaction.New(chain.GetConfig().Magic, script, 10000000)
withdrawTx.ValidUntilBlock = chain.blockHeight + 1
withdrawTx.NetworkFee = 10000000
withdrawTx.Signers = []transaction.Signer{
{
Account: acc.PrivateKey().PublicKey().GetScriptHash(),
Scopes: transaction.None,
},
}
err = acc.SignTx(withdrawTx)
require.NoError(t, err)
b := chain.newBlock(withdrawTx)
err = chain.AddBlock(b)
require.NoError(t, err)
appExecRes, err := chain.GetAppExecResults(withdrawTx.Hash(), trigger.Application)
require.NoError(t, err)
checkResult(t, &appExecRes[0], stackitem.NewBool(false))
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
// `withdraw`: locked deposit
withdrawRes, err := invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), acc.PrivateKey().PublicKey().GetScriptHash())
require.NoError(t, err)
checkResult(t, withdrawRes, stackitem.NewBool(false))
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
// `withdraw`: unlock deposit and transfer GAS back to owner
chain.genBlocks(depositLock)
withdrawRes, err = invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, withdrawRes, stackitem.NewBool(true))
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, balance, stackitem.Make(0))
checkBalanceOf(t, chain, notaryHash, 0)
// `withdraw`: the second time it should fail, because there's no deposit left
withdrawRes, err = invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), testchain.MultisigScriptHash())
require.NoError(t, err)
checkResult(t, withdrawRes, stackitem.NewBool(false))
}
func TestNotaryNodesReward(t *testing.T) {
checkReward := func(nKeys int, nNotaryNodes int, spendFullDeposit bool) {
chain := newTestChain(t)
defer chain.Close()
notaryHash := chain.contracts.Notary.Hash
gasHash := chain.contracts.GAS.Hash
signer := testchain.MultisigScriptHash()
var err error
// set Notary nodes and check their balance
notaryNodes := make([]*keys.PrivateKey, nNotaryNodes)
notaryNodesPublicKeys := make(keys.PublicKeys, nNotaryNodes)
for i := range notaryNodes {
notaryNodes[i], err = keys.NewPrivateKey()
require.NoError(t, err)
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey()
}
chain.setNodesByRole(t, true, native.RoleP2PNotary, notaryNodesPublicKeys)
for _, notaryNode := range notaryNodesPublicKeys {
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), 0)
}
// deposit GAS for `signer` with lock until the next block
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
if !spendFullDeposit {
depositAmount += 1_0000
}
transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, depositAmount, signer, int64(chain.BlockHeight()+1))
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
// send transaction with Notary contract as a sender
tx := chain.newTestTx(util.Uint160{}, []byte{byte(opcode.PUSH1)})
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
tx.Signers = []transaction.Signer{
{
Account: notaryHash,
Scopes: transaction.None,
},
{
Account: signer,
Scopes: transaction.None,
},
}
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].Sign(data)...),
},
{
InvocationScript: testchain.Sign(data),
VerificationScript: testchain.MultisigVerificationScript(),
},
}
b := chain.newBlock(tx)
require.NoError(t, chain.AddBlock(b))
checkBalanceOf(t, chain, notaryHash, int(depositAmount-tx.SystemFee-tx.NetworkFee))
for _, notaryNode := range notaryNodesPublicKeys {
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaryNodes)
}
}
for _, spendDeposit := range []bool{true, false} {
checkReward(0, 1, spendDeposit)
checkReward(0, 2, spendDeposit)
checkReward(1, 1, spendDeposit)
checkReward(1, 2, spendDeposit)
checkReward(1, 3, spendDeposit)
checkReward(5, 1, spendDeposit)
checkReward(5, 2, spendDeposit)
checkReward(5, 6, spendDeposit)
checkReward(5, 7, spendDeposit)
}
}

View file

@ -90,23 +90,14 @@ func getOracleContractState(h util.Uint160) *state.Contract {
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
url string, filter *string, userData []byte, gas int64) util.Uint256 {
w := io.NewBufBinWriter()
var filtItem interface{}
if filter != nil {
filtItem = *filter
}
emit.AppCallWithOperationAndArgs(w.BinWriter, h, "requestURL",
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL",
url, filtItem, "handle", userData, gas)
require.NoError(t, w.Err)
gas += 50_000_000 + 5_000_000 // request + contract call with args
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), gas)
tx.ValidUntilBlock = bc.BlockHeight() + 1
tx.NetworkFee = 1_000_000
setSigner(tx, testchain.MultisigScriptHash())
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
return tx.Hash()
require.NoError(t, err)
return res.Container
}
func TestOracle_Request(t *testing.T) {

View file

@ -5,18 +5,11 @@ import (
"testing"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
@ -24,6 +17,7 @@ import (
func TestMaxTransactionsPerBlock(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
policyHash := chain.contracts.Policy.Metadata().Hash
t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao)
@ -31,14 +25,14 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
})
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getMaxTransactionsPerBlock")
res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxTransactionsPerBlock")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(512)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
@ -47,7 +41,7 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
})
t.Run("set, too big value", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(block.MaxContentsPerBlock)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(block.MaxContentsPerBlock)))
require.NoError(t, err)
checkFAULTState(t, res)
})
@ -56,6 +50,7 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
func TestMaxBlockSize(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
policyHash := chain.contracts.Policy.Metadata().Hash
t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetMaxBlockSizeInternal(chain.dao)
@ -63,25 +58,25 @@ func TestMaxBlockSize(t *testing.T) {
})
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSize")
res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSize")
res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(102400)))
require.NoError(t, chain.persist())
})
t.Run("set, too big value", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSize", bigint.ToBytes(big.NewInt(payload.MaxSize+1)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(payload.MaxSize+1)))
require.NoError(t, err)
checkFAULTState(t, res)
})
@ -90,6 +85,7 @@ func TestMaxBlockSize(t *testing.T) {
func TestFeePerByte(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
policyHash := chain.contracts.Policy.Metadata().Hash
t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
@ -97,14 +93,14 @@ func TestFeePerByte(t *testing.T) {
})
t.Run("get, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getFeePerByte")
res, err := invokeContractMethod(chain, 100000000, policyHash, "getFeePerByte")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1000)))
require.NoError(t, chain.persist())
})
t.Run("set", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
@ -113,13 +109,13 @@ func TestFeePerByte(t *testing.T) {
})
t.Run("set, negative value", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(-1)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(-1)))
require.NoError(t, err)
checkFAULTState(t, res)
})
t.Run("set, too big value", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setFeePerByte", bigint.ToBytes(big.NewInt(100_000_000+1)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(100_000_000+1)))
require.NoError(t, err)
checkFAULTState(t, res)
})
@ -128,6 +124,7 @@ func TestFeePerByte(t *testing.T) {
func TestBlockSystemFee(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
policyHash := chain.contracts.Policy.Metadata().Hash
t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetMaxBlockSystemFeeInternal(chain.dao)
@ -135,24 +132,24 @@ func TestBlockSystemFee(t *testing.T) {
})
t.Run("get", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "getMaxBlockSystemFee")
res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(9000*native.GASFactor)))
require.NoError(t, chain.persist())
})
t.Run("set, too low fee", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(4007600)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(4007600)))
require.NoError(t, err)
checkFAULTState(t, res)
})
t.Run("set, success", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000)))
res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000)))
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
res, err = invokeNativePolicyMethod(chain, "getMaxBlockSystemFee")
res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000)))
require.NoError(t, chain.persist())
@ -163,6 +160,7 @@ func TestBlockedAccounts(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
account := util.Uint160{1, 2, 3}
policyHash := chain.contracts.Policy.Metadata().Hash
t.Run("isBlocked, internal method", func(t *testing.T) {
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160())
@ -170,14 +168,14 @@ func TestBlockedAccounts(t *testing.T) {
})
t.Run("isBlocked, contract method", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "isBlocked", random.Uint160())
res, err := invokeContractMethod(chain, 100000000, policyHash, "isBlocked", random.Uint160())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(false))
require.NoError(t, chain.persist())
})
t.Run("block-unblock account", func(t *testing.T) {
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
res, err := invokeContractMethod(chain, 100000000, policyHash, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
@ -185,7 +183,7 @@ func TestBlockedAccounts(t *testing.T) {
require.Equal(t, isBlocked, true)
require.NoError(t, chain.persist())
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
res, err = invokeContractMethod(chain, 100000000, policyHash, "unblockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
@ -196,65 +194,27 @@ func TestBlockedAccounts(t *testing.T) {
t.Run("double-block", func(t *testing.T) {
// block
res, err := invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
res, err := invokeContractMethod(chain, 100000000, policyHash, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
// double-block should fail
res, err = invokeNativePolicyMethod(chain, "blockAccount", account.BytesBE())
res, err = invokeContractMethod(chain, 100000000, policyHash, "blockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(false))
require.NoError(t, chain.persist())
// unblock
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
res, err = invokeContractMethod(chain, 100000000, policyHash, "unblockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.NoError(t, chain.persist())
// unblock the same account should fail as we don't have it blocked
res, err = invokeNativePolicyMethod(chain, "unblockAccount", account.BytesBE())
res, err = invokeContractMethod(chain, 100000000, policyHash, "unblockAccount", account.BytesBE())
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(false))
require.NoError(t, chain.persist())
})
}
func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interface{}) (*state.AppExecResult, error) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, chain.contracts.Policy.Metadata().Hash, method, args...)
if w.Err != nil {
return nil, w.Err
}
script := w.Bytes()
tx := transaction.New(chain.GetConfig().Magic, script, 10000000)
validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil
addSigners(tx)
err := testchain.SignTx(chain, tx)
if err != nil {
return nil, err
}
b := chain.newBlock(tx)
err = chain.AddBlock(b)
if err != nil {
return nil, err
}
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
if err != nil {
return nil, err
}
return &res[0], nil
}
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
require.Equal(t, vm.HaltState, result.VMState)
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected, result.Stack[0])
}
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
require.Equal(t, vm.FaultState, result.VMState)
}

26
pkg/core/state/deposit.go Normal file
View file

@ -0,0 +1,26 @@
package state
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// Deposit represents GAS deposit from Notary contract.
type Deposit struct {
Amount *big.Int
Till uint32
}
// EncodeBinary implements io.Serializable interface.
func (d *Deposit) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(bigint.ToBytes(d.Amount))
w.WriteU32LE(d.Till)
}
// DecodeBinary implements io.Serializable interface.
func (d *Deposit) DecodeBinary(r *io.BinReader) {
d.Amount = bigint.FromBytes(r.ReadVarBytes())
d.Till = r.ReadU32LE()
}

View file

@ -39,6 +39,8 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
attr.Value = new(NotValidBefore)
case ConflictsT:
attr.Value = new(Conflicts)
case NotaryAssistedT:
attr.Value = new(NotaryAssisted)
default:
if t >= ReservedLowerBound && t <= ReservedUpperBound {
attr.Value = new(Reserved)
@ -55,7 +57,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
bw.WriteB(byte(attr.Type))
switch t := attr.Type; t {
case HighPriority:
case OracleResponseT, NotValidBeforeT, ConflictsT:
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT:
attr.Value.EncodeBinary(bw)
default:
if t >= ReservedLowerBound && t <= ReservedUpperBound {
@ -97,6 +99,9 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
case ConflictsT.String():
attr.Type = ConflictsT
attr.Value = new(Conflicts)
case NotaryAssistedT.String():
attr.Type = NotaryAssistedT
attr.Value = new(NotaryAssisted)
default:
return errors.New("wrong Type")
}

View file

@ -7,6 +7,8 @@ import (
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
@ -29,13 +31,25 @@ func TestAttribute_EncodeBinary(t *testing.T) {
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
})
t.Run("NotValidBefore", func(t *testing.T) {
attr := &Attribute{
Type: NotValidBeforeT,
Value: &NotValidBefore{
Height: 123,
},
}
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
t.Run("positive", func(t *testing.T) {
attr := &Attribute{
Type: NotValidBeforeT,
Value: &NotValidBefore{
Height: 123,
},
}
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
})
t.Run("bad format: too long", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes([]byte{1, 2, 3, 4, 5})
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotValidBefore)))
})
t.Run("bad format: too short", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes([]byte{1, 2, 3})
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotValidBefore)))
})
})
t.Run("Reserved", func(t *testing.T) {
getReservedAttribute := func(t AttrType) *Attribute {
@ -47,7 +61,7 @@ func TestAttribute_EncodeBinary(t *testing.T) {
}
}
t.Run("lower bound", func(t *testing.T) {
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+2), new(Attribute))
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+3), new(Attribute))
})
t.Run("upper bound", func(t *testing.T) {
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedUpperBound), new(Attribute))
@ -61,13 +75,46 @@ func TestAttribute_EncodeBinary(t *testing.T) {
})
})
t.Run("Conflicts", func(t *testing.T) {
attr := &Attribute{
Type: ConflictsT,
Value: &Conflicts{
Hash: random.Uint256(),
},
}
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
t.Run("positive", func(t *testing.T) {
attr := &Attribute{
Type: ConflictsT,
Value: &Conflicts{
Hash: random.Uint256(),
},
}
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
})
t.Run("negative: too long", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes(make([]byte, util.Uint256Size+1))
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(Conflicts)))
})
t.Run("negative: bad uint256", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes(make([]byte, util.Uint256Size-1))
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(Conflicts)))
})
})
t.Run("NotaryAssisted", func(t *testing.T) {
t.Run("positive", func(t *testing.T) {
attr := &Attribute{
Type: NotaryAssistedT,
Value: &NotaryAssisted{
NKeys: 3,
},
}
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
})
t.Run("bad format: too long", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes(make([]byte, 2))
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotaryAssisted)))
})
t.Run("bad format: too short", func(t *testing.T) {
bw := io.NewBufBinWriter()
bw.WriteVarBytes([]byte{})
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotaryAssisted)))
})
})
}
@ -123,4 +170,13 @@ func TestAttribute_MarshalJSON(t *testing.T) {
}
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
})
t.Run("NotaryAssisted", func(t *testing.T) {
attr := &Attribute{
Type: NotaryAssistedT,
Value: &NotaryAssisted{
NKeys: 3,
},
}
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
})
}

View file

@ -18,6 +18,7 @@ const (
OracleResponseT AttrType = 0x11 // OracleResponse
NotValidBeforeT AttrType = ReservedLowerBound // NotValidBefore
ConflictsT AttrType = ReservedLowerBound + 1 // Conflicts
NotaryAssistedT AttrType = ReservedLowerBound + 2 // NotaryAssisted
)
func (a AttrType) allowMultiple() bool {

View file

@ -12,16 +12,17 @@ func _() {
_ = x[OracleResponseT-17]
_ = x[NotValidBeforeT-224]
_ = x[ConflictsT-225]
_ = x[NotaryAssistedT-226]
}
const (
_AttrType_name_0 = "HighPriority"
_AttrType_name_1 = "OracleResponse"
_AttrType_name_2 = "NotValidBeforeConflicts"
_AttrType_name_2 = "NotValidBeforeConflictsNotaryAssisted"
)
var (
_AttrType_index_2 = [...]uint8{0, 14, 23}
_AttrType_index_2 = [...]uint8{0, 14, 23, 37}
)
func (i AttrType) String() string {
@ -30,7 +31,7 @@ func (i AttrType) String() string {
return _AttrType_name_0
case i == 17:
return _AttrType_name_1
case 224 <= i && i <= 225:
case 224 <= i && i <= 226:
i -= 224
return _AttrType_name_2[_AttrType_index_2[i]:_AttrType_index_2[i+1]]
default:

View file

@ -12,7 +12,11 @@ type Conflicts struct {
// DecodeBinary implements io.Serializable interface.
func (c *Conflicts) DecodeBinary(br *io.BinReader) {
hash, err := util.Uint256DecodeBytesBE(br.ReadVarBytes(util.Uint256Size))
bytes := br.ReadVarBytes(util.Uint256Size)
if br.Err != nil {
return
}
hash, err := util.Uint256DecodeBytesBE(bytes)
if err != nil {
br.Err = err
return

View file

@ -2,6 +2,7 @@ package transaction
import (
"encoding/binary"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/io"
)
@ -14,6 +15,13 @@ type NotValidBefore struct {
// DecodeBinary implements io.Serializable interface.
func (n *NotValidBefore) DecodeBinary(br *io.BinReader) {
bytes := br.ReadVarBytes(4)
if br.Err != nil {
return
}
if len(bytes) != 4 {
br.Err = fmt.Errorf("expected 4 bytes, got %d", len(bytes))
return
}
n.Height = binary.LittleEndian.Uint32(bytes)
}

View file

@ -0,0 +1,37 @@
package transaction
import (
"fmt"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// NotaryServiceFeePerKey is a reward per key for notary nodes.
const NotaryServiceFeePerKey = 1000_0000 // 0.1 GAS
// NotaryAssisted represents attribute for notary service transactions.
type NotaryAssisted struct {
NKeys uint8 `json:"nkeys"`
}
// DecodeBinary implements io.Serializable interface.
func (n *NotaryAssisted) DecodeBinary(br *io.BinReader) {
bytes := br.ReadVarBytes(1)
if br.Err != nil {
return
}
if len(bytes) != 1 {
br.Err = fmt.Errorf("expected 1 byte, got %d", len(bytes))
return
}
n.NKeys = bytes[0]
}
// EncodeBinary implements io.Serializable interface.
func (n *NotaryAssisted) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes([]byte{n.NKeys})
}
func (n *NotaryAssisted) toJSONMap(m map[string]interface{}) {
m["nkeys"] = n.NKeys
}

View file

@ -45,6 +45,14 @@ type Context struct {
// Call flags this context was created with.
callFlag smartcontract.CallFlag
// InvocationState contains expected return type and actions to be performed on context unload.
InvocationState
}
// InvocationState contains return convention and callback to be executed on context unload.
type InvocationState struct {
// Callback is executed on context unload.
Callback func(ctx *Context)
// CheckReturn specifies if amount of return values needs to be checked.
CheckReturn CheckReturnState
}

View file

@ -1410,6 +1410,9 @@ func (v *VM) unloadContext(ctx *Context) {
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
ctx.static.Clear()
}
if ctx.Callback != nil {
ctx.Callback(ctx)
}
switch ctx.CheckReturn {
case NoCheck:
case EnsureIsEmpty: