github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/notary.go (about)

     1  package native
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"math/big"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/config"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
    17  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    18  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
    19  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    20  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    21  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    22  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    23  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    24  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    25  	"github.com/nspcc-dev/neo-go/pkg/util"
    26  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    27  )
    28  
    29  // Notary represents Notary native contract.
    30  type Notary struct {
    31  	interop.ContractMD
    32  	GAS    *GAS
    33  	NEO    *NEO
    34  	Desig  *Designate
    35  	Policy *Policy
    36  }
    37  
    38  type NotaryCache struct {
    39  	maxNotValidBeforeDelta uint32
    40  }
    41  
    42  // NotaryService is a Notary module interface.
    43  type NotaryService interface {
    44  	UpdateNotaryNodes(pubs keys.PublicKeys)
    45  }
    46  
    47  const (
    48  	notaryContractID = -10
    49  	// prefixDeposit is a prefix for storing Notary deposits.
    50  	prefixDeposit                 = 1
    51  	defaultDepositDeltaTill       = 5760
    52  	defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour
    53  )
    54  
    55  var maxNotValidBeforeDeltaKey = []byte{10}
    56  
    57  var (
    58  	_ interop.Contract        = (*Notary)(nil)
    59  	_ dao.NativeContractCache = (*NotaryCache)(nil)
    60  )
    61  
    62  // Copy implements NativeContractCache interface.
    63  func (c *NotaryCache) Copy() dao.NativeContractCache {
    64  	cp := &NotaryCache{}
    65  	copyNotaryCache(c, cp)
    66  	return cp
    67  }
    68  
    69  func copyNotaryCache(src, dst *NotaryCache) {
    70  	*dst = *src
    71  }
    72  
    73  // newNotary returns Notary native contract.
    74  func newNotary() *Notary {
    75  	n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)}
    76  	defer n.BuildHFSpecificMD(n.ActiveIn())
    77  
    78  	desc := newDescriptor("onNEP17Payment", smartcontract.VoidType,
    79  		manifest.NewParameter("from", smartcontract.Hash160Type),
    80  		manifest.NewParameter("amount", smartcontract.IntegerType),
    81  		manifest.NewParameter("data", smartcontract.AnyType))
    82  	md := newMethodAndPrice(n.onPayment, 1<<15, callflag.States)
    83  	n.AddMethod(md, desc)
    84  
    85  	desc = newDescriptor("lockDepositUntil", smartcontract.BoolType,
    86  		manifest.NewParameter("address", smartcontract.Hash160Type),
    87  		manifest.NewParameter("till", smartcontract.IntegerType))
    88  	md = newMethodAndPrice(n.lockDepositUntil, 1<<15, callflag.States)
    89  	n.AddMethod(md, desc)
    90  
    91  	desc = newDescriptor("withdraw", smartcontract.BoolType,
    92  		manifest.NewParameter("from", smartcontract.Hash160Type),
    93  		manifest.NewParameter("to", smartcontract.Hash160Type))
    94  	md = newMethodAndPrice(n.withdraw, 1<<15, callflag.All)
    95  	n.AddMethod(md, desc)
    96  
    97  	desc = newDescriptor("balanceOf", smartcontract.IntegerType,
    98  		manifest.NewParameter("addr", smartcontract.Hash160Type))
    99  	md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates)
   100  	n.AddMethod(md, desc)
   101  
   102  	desc = newDescriptor("expirationOf", smartcontract.IntegerType,
   103  		manifest.NewParameter("addr", smartcontract.Hash160Type))
   104  	md = newMethodAndPrice(n.expirationOf, 1<<15, callflag.ReadStates)
   105  	n.AddMethod(md, desc)
   106  
   107  	desc = newDescriptor("verify", smartcontract.BoolType,
   108  		manifest.NewParameter("signature", smartcontract.SignatureType))
   109  	md = newMethodAndPrice(n.verify, nativeprices.NotaryVerificationPrice, callflag.ReadStates)
   110  	n.AddMethod(md, desc)
   111  
   112  	desc = newDescriptor("getMaxNotValidBeforeDelta", smartcontract.IntegerType)
   113  	md = newMethodAndPrice(n.getMaxNotValidBeforeDelta, 1<<15, callflag.ReadStates)
   114  	n.AddMethod(md, desc)
   115  
   116  	desc = newDescriptor("setMaxNotValidBeforeDelta", smartcontract.VoidType,
   117  		manifest.NewParameter("value", smartcontract.IntegerType))
   118  	md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 1<<15, callflag.States)
   119  	n.AddMethod(md, desc)
   120  
   121  	return n
   122  }
   123  
   124  // Metadata implements the Contract interface.
   125  func (n *Notary) Metadata() *interop.ContractMD {
   126  	return &n.ContractMD
   127  }
   128  
   129  // Initialize initializes Notary native contract and implements the Contract interface.
   130  func (n *Notary) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
   131  	if hf != n.ActiveIn() {
   132  		return nil
   133  	}
   134  
   135  	setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
   136  
   137  	cache := &NotaryCache{
   138  		maxNotValidBeforeDelta: defaultMaxNotValidBeforeDelta,
   139  	}
   140  	ic.DAO.SetCache(n.ID, cache)
   141  	return nil
   142  }
   143  
   144  func (n *Notary) InitializeCache(blockHeight uint32, d *dao.Simple) error {
   145  	cache := &NotaryCache{
   146  		maxNotValidBeforeDelta: uint32(getIntWithKey(n.ID, d, maxNotValidBeforeDeltaKey)),
   147  	}
   148  
   149  	d.SetCache(n.ID, cache)
   150  	return nil
   151  }
   152  
   153  // OnPersist implements the Contract interface.
   154  func (n *Notary) OnPersist(ic *interop.Context) error {
   155  	var (
   156  		nFees    int64
   157  		notaries keys.PublicKeys
   158  		err      error
   159  	)
   160  	for _, tx := range ic.Block.Transactions {
   161  		if tx.HasAttribute(transaction.NotaryAssistedT) {
   162  			if notaries == nil {
   163  				notaries, err = n.GetNotaryNodes(ic.DAO)
   164  				if err != nil {
   165  					return fmt.Errorf("failed to get notary nodes: %w", err)
   166  				}
   167  			}
   168  			nKeys := tx.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys
   169  			nFees += int64(nKeys) + 1
   170  			if tx.Sender() == n.Hash {
   171  				payer := tx.Signers[1]
   172  				balance := n.GetDepositFor(ic.DAO, payer.Account)
   173  				balance.Amount.Sub(balance.Amount, big.NewInt(tx.SystemFee+tx.NetworkFee))
   174  				if balance.Amount.Sign() == 0 {
   175  					n.removeDepositFor(ic.DAO, payer.Account)
   176  				} else {
   177  					err := n.putDepositFor(ic.DAO, balance, payer.Account)
   178  					if err != nil {
   179  						return fmt.Errorf("failed to update deposit for %s: %w", payer.Account.StringBE(), err)
   180  					}
   181  				}
   182  			}
   183  		}
   184  	}
   185  	if nFees == 0 {
   186  		return nil
   187  	}
   188  	feePerKey := n.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
   189  	singleReward := calculateNotaryReward(nFees, feePerKey, len(notaries))
   190  	for _, notary := range notaries {
   191  		n.GAS.mint(ic, notary.GetScriptHash(), singleReward, false)
   192  	}
   193  	return nil
   194  }
   195  
   196  // PostPersist implements the Contract interface.
   197  func (n *Notary) PostPersist(ic *interop.Context) error {
   198  	return nil
   199  }
   200  
   201  // ActiveIn implements the Contract interface.
   202  func (n *Notary) ActiveIn() *config.Hardfork {
   203  	return nil
   204  }
   205  
   206  // onPayment records the deposited amount as belonging to "from" address with a lock
   207  // till the specified chain's height.
   208  func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   209  	if h := ic.VM.GetCallingScriptHash(); h != n.GAS.Hash {
   210  		panic(fmt.Errorf("only GAS can be accepted for deposit, got %s", h.StringBE()))
   211  	}
   212  	from := toUint160(args[0])
   213  	to := from
   214  	amount := toBigInt(args[1])
   215  	data, ok := args[2].(*stackitem.Array)
   216  	if !ok || len(data.Value().([]stackitem.Item)) != 2 {
   217  		panic(errors.New("`data` parameter should be an array of 2 elements"))
   218  	}
   219  	additionalParams := data.Value().([]stackitem.Item)
   220  	if !additionalParams[0].Equals(stackitem.Null{}) {
   221  		to = toUint160(additionalParams[0])
   222  	}
   223  
   224  	allowedChangeTill := ic.Tx.Sender() == to
   225  	currentHeight := ic.BlockHeight()
   226  	deposit := n.GetDepositFor(ic.DAO, to)
   227  	till := toUint32(additionalParams[1])
   228  	if till < currentHeight+2 {
   229  		panic(fmt.Errorf("`till` shouldn't be less than the chain's height + 1 (%d at min)", currentHeight+2))
   230  	}
   231  	if deposit != nil && till < deposit.Till {
   232  		panic(fmt.Errorf("`till` shouldn't be less than the previous value %d", deposit.Till))
   233  	}
   234  	feePerKey := n.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
   235  	if deposit == nil {
   236  		if amount.Cmp(big.NewInt(2*feePerKey)) < 0 {
   237  			panic(fmt.Errorf("first deposit can not be less than %d, got %d", 2*feePerKey, amount.Int64()))
   238  		}
   239  		deposit = &state.Deposit{
   240  			Amount: new(big.Int),
   241  		}
   242  		if !allowedChangeTill {
   243  			till = currentHeight + defaultDepositDeltaTill
   244  		}
   245  	} else if !allowedChangeTill { // only deposit's owner is allowed to set or update `till`
   246  		till = deposit.Till
   247  	}
   248  	deposit.Amount.Add(deposit.Amount, amount)
   249  	deposit.Till = till
   250  
   251  	if err := n.putDepositFor(ic.DAO, deposit, to); err != nil {
   252  		panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", from.StringBE(), err))
   253  	}
   254  	return stackitem.Null{}
   255  }
   256  
   257  // lockDepositUntil updates the chain's height until which the deposit is locked.
   258  func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   259  	addr := toUint160(args[0])
   260  	ok, err := runtime.CheckHashedWitness(ic, addr)
   261  	if err != nil {
   262  		panic(fmt.Errorf("failed to check witness for %s: %w", addr.StringBE(), err))
   263  	}
   264  	if !ok {
   265  		return stackitem.NewBool(false)
   266  	}
   267  	till := toUint32(args[1])
   268  	if till < (ic.BlockHeight() + 1 + 1) { // deposit can't expire at the current persisting block.
   269  		return stackitem.NewBool(false)
   270  	}
   271  	deposit := n.GetDepositFor(ic.DAO, addr)
   272  	if deposit == nil {
   273  		return stackitem.NewBool(false)
   274  	}
   275  	if till < deposit.Till {
   276  		return stackitem.NewBool(false)
   277  	}
   278  	deposit.Till = till
   279  	err = n.putDepositFor(ic.DAO, deposit, addr)
   280  	if err != nil {
   281  		panic(fmt.Errorf("failed to put deposit for %s into the storage: %w", addr.StringBE(), err))
   282  	}
   283  	return stackitem.NewBool(true)
   284  }
   285  
   286  // withdraw sends all deposited GAS for "from" address to "to" address.
   287  func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   288  	from := toUint160(args[0])
   289  	ok, err := runtime.CheckHashedWitness(ic, from)
   290  	if err != nil {
   291  		panic(fmt.Errorf("failed to check witness for %s: %w", from.StringBE(), err))
   292  	}
   293  	if !ok {
   294  		return stackitem.NewBool(false)
   295  	}
   296  	to := from
   297  	if !args[1].Equals(stackitem.Null{}) {
   298  		to = toUint160(args[1])
   299  	}
   300  	deposit := n.GetDepositFor(ic.DAO, from)
   301  	if deposit == nil {
   302  		return stackitem.NewBool(false)
   303  	}
   304  	// Allow withdrawal only after `till` block was persisted, thus, use ic.BlockHeight().
   305  	if ic.BlockHeight() < deposit.Till {
   306  		return stackitem.NewBool(false)
   307  	}
   308  	cs, err := ic.GetContract(n.GAS.Hash)
   309  	if err != nil {
   310  		panic(fmt.Errorf("failed to get GAS contract state: %w", err))
   311  	}
   312  
   313  	// Remove deposit before GAS transfer processing to avoid double-withdrawal.
   314  	// In case if Gas contract call fails, state will be rolled back.
   315  	n.removeDepositFor(ic.DAO, from)
   316  
   317  	transferArgs := []stackitem.Item{stackitem.NewByteArray(n.Hash.BytesBE()), stackitem.NewByteArray(to.BytesBE()), stackitem.NewBigInteger(deposit.Amount), stackitem.Null{}}
   318  	err = contract.CallFromNative(ic, n.Hash, cs, "transfer", transferArgs, true)
   319  	if err != nil {
   320  		panic(fmt.Errorf("failed to transfer GAS from Notary account: %w", err))
   321  	}
   322  	if !ic.VM.Estack().Pop().Bool() {
   323  		panic("failed to transfer GAS from Notary account: `transfer` returned false")
   324  	}
   325  	return stackitem.NewBool(true)
   326  }
   327  
   328  // balanceOf returns the deposited GAS amount for the specified address.
   329  func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   330  	acc := toUint160(args[0])
   331  	return stackitem.NewBigInteger(n.BalanceOf(ic.DAO, acc))
   332  }
   333  
   334  // BalanceOf is an internal representation of `balanceOf` Notary method.
   335  func (n *Notary) BalanceOf(dao *dao.Simple, acc util.Uint160) *big.Int {
   336  	deposit := n.GetDepositFor(dao, acc)
   337  	if deposit == nil {
   338  		return big.NewInt(0)
   339  	}
   340  	return deposit.Amount
   341  }
   342  
   343  // expirationOf returns the deposit lock height for the specified address.
   344  func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   345  	acc := toUint160(args[0])
   346  	return stackitem.Make(n.ExpirationOf(ic.DAO, acc))
   347  }
   348  
   349  // ExpirationOf is an internal representation of `expirationOf` Notary method.
   350  func (n *Notary) ExpirationOf(dao *dao.Simple, acc util.Uint160) uint32 {
   351  	deposit := n.GetDepositFor(dao, acc)
   352  	if deposit == nil {
   353  		return 0
   354  	}
   355  	return deposit.Till
   356  }
   357  
   358  // verify checks whether the transaction was signed by one of the notaries.
   359  func (n *Notary) verify(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   360  	sig, err := args[0].TryBytes()
   361  	if err != nil {
   362  		panic(fmt.Errorf("failed to get signature bytes: %w", err))
   363  	}
   364  	tx := ic.Tx
   365  	if len(tx.GetAttributes(transaction.NotaryAssistedT)) == 0 {
   366  		return stackitem.NewBool(false)
   367  	}
   368  	for _, signer := range tx.Signers {
   369  		if signer.Account == n.Hash {
   370  			if signer.Scopes != transaction.None {
   371  				return stackitem.NewBool(false)
   372  			}
   373  			break
   374  		}
   375  	}
   376  	if tx.Sender() == n.Hash {
   377  		if len(tx.Signers) != 2 {
   378  			return stackitem.NewBool(false)
   379  		}
   380  		payer := tx.Signers[1].Account
   381  		balance := n.GetDepositFor(ic.DAO, payer)
   382  		if balance == nil || balance.Amount.Cmp(big.NewInt(tx.NetworkFee+tx.SystemFee)) < 0 {
   383  			return stackitem.NewBool(false)
   384  		}
   385  	}
   386  	notaries, err := n.GetNotaryNodes(ic.DAO)
   387  	if err != nil {
   388  		panic(fmt.Errorf("failed to get notary nodes: %w", err))
   389  	}
   390  	shash := hash.NetSha256(uint32(ic.Network), tx)
   391  	var verified bool
   392  	for _, n := range notaries {
   393  		if n.Verify(sig, shash[:]) {
   394  			verified = true
   395  			break
   396  		}
   397  	}
   398  	return stackitem.NewBool(verified)
   399  }
   400  
   401  // GetNotaryNodes returns public keys of notary nodes.
   402  func (n *Notary) GetNotaryNodes(d *dao.Simple) (keys.PublicKeys, error) {
   403  	nodes, _, err := n.Desig.GetDesignatedByRole(d, noderoles.P2PNotary, math.MaxUint32)
   404  	return nodes, err
   405  }
   406  
   407  // getMaxNotValidBeforeDelta is a Notary contract method and returns the maximum NotValidBefore delta.
   408  func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   409  	return stackitem.NewBigInteger(big.NewInt(int64(n.GetMaxNotValidBeforeDelta(ic.DAO))))
   410  }
   411  
   412  // GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method.
   413  func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 {
   414  	cache := dao.GetROCache(n.ID).(*NotaryCache)
   415  	return cache.maxNotValidBeforeDelta
   416  }
   417  
   418  // setMaxNotValidBeforeDelta is a Notary contract method and sets the maximum NotValidBefore delta.
   419  func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   420  	value := toUint32(args[0])
   421  	cfg := ic.Chain.GetConfig()
   422  	maxInc := cfg.MaxValidUntilBlockIncrement
   423  	if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.BlockHeight())) {
   424  		panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, cfg.GetNumOfCNs(ic.BlockHeight())))
   425  	}
   426  	if !n.NEO.checkCommittee(ic) {
   427  		panic("invalid committee signature")
   428  	}
   429  	setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value))
   430  	cache := ic.DAO.GetRWCache(n.ID).(*NotaryCache)
   431  	cache.maxNotValidBeforeDelta = value
   432  	return stackitem.Null{}
   433  }
   434  
   435  // GetDepositFor returns state.Deposit for the account specified. It returns nil in case
   436  // the deposit is not found in the storage and panics in case of any other error.
   437  func (n *Notary) GetDepositFor(dao *dao.Simple, acc util.Uint160) *state.Deposit {
   438  	key := append([]byte{prefixDeposit}, acc.BytesBE()...)
   439  	deposit := new(state.Deposit)
   440  	err := getConvertibleFromDAO(n.ID, dao, key, deposit)
   441  	if err == nil {
   442  		return deposit
   443  	}
   444  	if errors.Is(err, storage.ErrKeyNotFound) {
   445  		return nil
   446  	}
   447  	panic(fmt.Errorf("failed to get deposit for %s from storage: %w", acc.StringBE(), err))
   448  }
   449  
   450  // putDepositFor puts the deposit on the balance of the specified account in the storage.
   451  func (n *Notary) putDepositFor(dao *dao.Simple, deposit *state.Deposit, acc util.Uint160) error {
   452  	key := append([]byte{prefixDeposit}, acc.BytesBE()...)
   453  	return putConvertibleToDAO(n.ID, dao, key, deposit)
   454  }
   455  
   456  // removeDepositFor removes the deposit from the storage.
   457  func (n *Notary) removeDepositFor(dao *dao.Simple, acc util.Uint160) {
   458  	key := append([]byte{prefixDeposit}, acc.BytesBE()...)
   459  	dao.DeleteStorageItem(n.ID, key)
   460  }
   461  
   462  // calculateNotaryReward calculates the reward for a single notary node based on FEE's count and Notary nodes count.
   463  func calculateNotaryReward(nFees int64, feePerKey int64, notariesCount int) *big.Int {
   464  	return big.NewInt(nFees * feePerKey / int64(notariesCount))
   465  }