github.com/aergoio/aergo@v1.3.1/types/transaction.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/big"
     8  	"strings"
     9  
    10  	"github.com/aergoio/aergo/fee"
    11  	"github.com/gogo/protobuf/proto"
    12  	"github.com/mr-tron/base58/base58"
    13  )
    14  
    15  //governance type transaction which has aergo.system in recipient
    16  
    17  const Stake = "v1stake"
    18  const Unstake = "v1unstake"
    19  const SetContractOwner = "v1setOwner"
    20  const NameCreate = "v1createName"
    21  const NameUpdate = "v1updateName"
    22  
    23  const TxMaxSize = 200 * 1024
    24  
    25  type validator func(tx *TxBody) error
    26  
    27  var govValidators map[string]validator
    28  
    29  func InitGovernance(consensus string, isPublic bool) {
    30  	sysValidator := ValidateSystemTx
    31  	if consensus != "dpos" {
    32  		sysValidator = func(tx *TxBody) error {
    33  			return ErrTxInvalidType
    34  		}
    35  	}
    36  
    37  	govValidators = map[string]validator{
    38  		AergoSystem: sysValidator,
    39  		AergoName:   validateNameTx,
    40  		AergoEnterprise: func(tx *TxBody) error {
    41  			if isPublic {
    42  				return ErrTxOnlySupportedInPriv
    43  			}
    44  			return nil
    45  		},
    46  	}
    47  }
    48  
    49  type Transaction interface {
    50  	GetTx() *Tx
    51  	GetBody() *TxBody
    52  	GetHash() []byte
    53  	CalculateTxHash() []byte
    54  	Validate([]byte, bool) error
    55  	ValidateWithSenderState(senderState *State) error
    56  	HasVerifedAccount() bool
    57  	GetVerifedAccount() Address
    58  	SetVerifedAccount(account Address) bool
    59  	RemoveVerifedAccount() bool
    60  	GetMaxFee() *big.Int
    61  }
    62  
    63  type transaction struct {
    64  	Tx              *Tx
    65  	VerifiedAccount Address
    66  }
    67  
    68  var _ Transaction = (*transaction)(nil)
    69  
    70  func NewTransaction(tx *Tx) Transaction {
    71  	return &transaction{Tx: tx}
    72  }
    73  
    74  func (tx *transaction) GetTx() *Tx {
    75  	if tx != nil {
    76  		return tx.Tx
    77  	}
    78  	return nil
    79  }
    80  
    81  func (tx *transaction) GetBody() *TxBody {
    82  	return tx.Tx.Body
    83  }
    84  
    85  func (tx *transaction) GetHash() []byte {
    86  	return tx.Tx.Hash
    87  }
    88  
    89  func (tx *transaction) CalculateTxHash() []byte {
    90  	return tx.Tx.CalculateTxHash()
    91  }
    92  
    93  func (tx *transaction) Validate(chainidhash []byte, isPublic bool) error {
    94  	if tx.GetTx() == nil || tx.GetTx().GetBody() == nil {
    95  		return ErrTxFormatInvalid
    96  	}
    97  
    98  	if !bytes.Equal(chainidhash, tx.GetTx().GetBody().GetChainIdHash()) {
    99  		return ErrTxInvalidChainIdHash
   100  	}
   101  	if proto.Size(tx.GetTx()) > TxMaxSize {
   102  		return ErrTxInvalidSize
   103  	}
   104  
   105  	account := tx.GetBody().GetAccount()
   106  	if account == nil {
   107  		return ErrTxFormatInvalid
   108  	}
   109  	if !bytes.Equal(tx.GetHash(), tx.CalculateTxHash()) {
   110  		return ErrTxHasInvalidHash
   111  	}
   112  
   113  	amount := tx.GetBody().GetAmountBigInt()
   114  	if amount.Cmp(MaxAER) > 0 {
   115  		return ErrTxInvalidAmount
   116  	}
   117  
   118  	gasprice := tx.GetBody().GetGasPriceBigInt()
   119  	if gasprice.Cmp(MaxAER) > 0 {
   120  		return ErrTxInvalidPrice
   121  	}
   122  
   123  	if len(tx.GetBody().GetAccount()) > AddressLength {
   124  		return ErrTxInvalidAccount
   125  	}
   126  
   127  	if len(tx.GetBody().GetRecipient()) > AddressLength {
   128  		return ErrTxInvalidRecipient
   129  	}
   130  
   131  	switch tx.GetBody().Type {
   132  	case TxType_REDEPLOY:
   133  		if isPublic {
   134  			return ErrTxInvalidType
   135  		}
   136  		if tx.GetBody().GetRecipient() == nil {
   137  			return ErrTxInvalidRecipient
   138  		}
   139  		fallthrough
   140  	case TxType_NORMAL:
   141  		if tx.GetBody().GetRecipient() == nil && len(tx.GetBody().GetPayload()) == 0 {
   142  			//contract deploy
   143  			return ErrTxInvalidRecipient
   144  		}
   145  	case TxType_GOVERNANCE:
   146  		if len(tx.GetBody().GetPayload()) <= 0 {
   147  			return ErrTxFormatInvalid
   148  		}
   149  
   150  		if err := validate(tx.GetBody()); err != nil {
   151  			return err
   152  		}
   153  
   154  	default:
   155  		return ErrTxInvalidType
   156  	}
   157  	return nil
   158  }
   159  
   160  func validate(tx *TxBody) error {
   161  	if val, exist := govValidators[string(tx.GetRecipient())]; exist {
   162  		return val(tx)
   163  	}
   164  
   165  	return ErrTxInvalidRecipient
   166  }
   167  
   168  func ValidateSystemTx(tx *TxBody) error {
   169  	var ci CallInfo
   170  	if err := json.Unmarshal(tx.Payload, &ci); err != nil {
   171  		return ErrTxInvalidPayload
   172  	}
   173  	switch ci.Name {
   174  	case Stake,
   175  		Unstake:
   176  	case VoteBP:
   177  		unique := map[string]int{}
   178  		for i, v := range ci.Args {
   179  			if i >= MaxCandidates {
   180  				return ErrTxInvalidPayload
   181  			}
   182  			encoded, ok := v.(string)
   183  			if !ok {
   184  				return ErrTxInvalidPayload
   185  			}
   186  			if unique[encoded] != 0 {
   187  				return ErrTxInvalidPayload
   188  			}
   189  			unique[encoded]++
   190  			candidate, err := base58.Decode(encoded)
   191  			if err != nil {
   192  				return ErrTxInvalidPayload
   193  			}
   194  			_, err = IDFromBytes(candidate)
   195  			if err != nil {
   196  				return ErrTxInvalidPayload
   197  			}
   198  		}
   199  		/* TODO: will be changed
   200  		case VoteNumBP,
   201  			VoteGasPrice,
   202  			VoteNamePrice,
   203  			VoteMinStaking:
   204  			for i, v := range ci.Args {
   205  				if i > 1 {
   206  					return ErrTxInvalidPayload
   207  				}
   208  				vstr, ok := v.(string)
   209  				if !ok {
   210  					return ErrTxInvalidPayload
   211  				}
   212  				if _, ok := new(big.Int).SetString(vstr, 10); !ok {
   213  					return ErrTxInvalidPayload
   214  				}
   215  			}
   216  		*/
   217  	default:
   218  		return ErrTxInvalidPayload
   219  	}
   220  	return nil
   221  }
   222  
   223  func validateNameTx(tx *TxBody) error {
   224  	var ci CallInfo
   225  	if err := json.Unmarshal(tx.Payload, &ci); err != nil {
   226  		return ErrTxInvalidPayload
   227  	}
   228  	switch ci.Name {
   229  	case NameCreate:
   230  		if err := _validateNameTx(tx, &ci); err != nil {
   231  			return err
   232  		}
   233  		if len(ci.Args) != 1 {
   234  			return fmt.Errorf("invalid arguments in %s", ci)
   235  		}
   236  	case NameUpdate:
   237  		if err := _validateNameTx(tx, &ci); err != nil {
   238  			return err
   239  		}
   240  		if len(ci.Args) != 2 {
   241  			return fmt.Errorf("invalid arguments in %s", ci)
   242  		}
   243  		to, err := DecodeAddress(ci.Args[1].(string))
   244  		if err != nil {
   245  			return fmt.Errorf("invalid receiver in %s", ci)
   246  		}
   247  		if len(to) > AddressLength {
   248  			return fmt.Errorf("too long name %s", string(tx.GetPayload()))
   249  		}
   250  	case SetContractOwner:
   251  		owner, ok := ci.Args[0].(string)
   252  		if !ok {
   253  			return fmt.Errorf("invalid arguments in %s", owner)
   254  		}
   255  		_, err := DecodeAddress(owner)
   256  		if err != nil {
   257  			return fmt.Errorf("invalid new owner %s", err.Error())
   258  		}
   259  	default:
   260  		return ErrTxInvalidPayload
   261  	}
   262  	return nil
   263  }
   264  
   265  func _validateNameTx(tx *TxBody, ci *CallInfo) error {
   266  	if len(ci.Args) < 1 {
   267  		return fmt.Errorf("invalid arguments in %s", ci)
   268  	}
   269  	nameParam, ok := ci.Args[0].(string)
   270  	if !ok {
   271  		return fmt.Errorf("invalid arguments in %s", nameParam)
   272  	}
   273  
   274  	if len(nameParam) > NameLength {
   275  		return fmt.Errorf("too long name %s", string(tx.GetPayload()))
   276  	}
   277  	if len(nameParam) != NameLength {
   278  		return fmt.Errorf("not supported yet")
   279  	}
   280  	if err := validateAllowedChar([]byte(nameParam)); err != nil {
   281  		return err
   282  	}
   283  	if new(big.Int).SetUint64(1000000000000000000).Cmp(tx.GetAmountBigInt()) > 0 {
   284  		return ErrTooSmallAmount
   285  	}
   286  	return nil
   287  
   288  }
   289  
   290  func (tx *transaction) ValidateWithSenderState(senderState *State) error {
   291  	if (senderState.GetNonce() + 1) > tx.GetBody().GetNonce() {
   292  		return ErrTxNonceTooLow
   293  	}
   294  	amount := tx.GetBody().GetAmountBigInt()
   295  	balance := senderState.GetBalanceBigInt()
   296  	switch tx.GetBody().GetType() {
   297  	case TxType_NORMAL, TxType_REDEPLOY:
   298  		spending := new(big.Int).Add(amount, tx.GetMaxFee())
   299  		if spending.Cmp(balance) > 0 {
   300  			return ErrInsufficientBalance
   301  		}
   302  	case TxType_GOVERNANCE:
   303  		switch string(tx.GetBody().GetRecipient()) {
   304  		case AergoSystem:
   305  			var ci CallInfo
   306  			if err := json.Unmarshal(tx.GetBody().GetPayload(), &ci); err != nil {
   307  				return ErrTxInvalidPayload
   308  			}
   309  			if ci.Name == Stake &&
   310  				amount.Cmp(balance) > 0 {
   311  				return ErrInsufficientBalance
   312  			}
   313  		case AergoName:
   314  		case AergoEnterprise:
   315  		default:
   316  			return ErrTxInvalidRecipient
   317  		}
   318  	}
   319  	if (senderState.GetNonce() + 1) < tx.GetBody().GetNonce() {
   320  		return ErrTxNonceToohigh
   321  	}
   322  	return nil
   323  }
   324  
   325  //TODO : refoctor after ContractState move to types
   326  func (tx *Tx) ValidateWithContractState(contractState *State) error {
   327  	//in system.ValidateSystemTx
   328  	//in name.ValidateNameTx
   329  	return nil
   330  }
   331  
   332  func (tx *transaction) GetVerifedAccount() Address {
   333  	return tx.VerifiedAccount
   334  }
   335  
   336  func (tx *transaction) SetVerifedAccount(account Address) bool {
   337  	tx.VerifiedAccount = account
   338  	return true
   339  }
   340  
   341  func (tx *transaction) HasVerifedAccount() bool {
   342  	return len(tx.VerifiedAccount) != 0
   343  }
   344  
   345  func (tx *transaction) RemoveVerifedAccount() bool {
   346  	return tx.SetVerifedAccount(nil)
   347  }
   348  
   349  func (tx *transaction) Clone() *transaction {
   350  	if tx == nil {
   351  		return nil
   352  	}
   353  	if tx.GetBody() == nil {
   354  		return &transaction{}
   355  	}
   356  	body := &TxBody{
   357  		Nonce:     tx.GetBody().Nonce,
   358  		Account:   Clone(tx.GetBody().Account).([]byte),
   359  		Recipient: Clone(tx.GetBody().Recipient).([]byte),
   360  		Amount:    Clone(tx.GetBody().Amount).([]byte),
   361  		Payload:   Clone(tx.GetBody().Payload).([]byte),
   362  		GasLimit:  tx.GetBody().GasLimit,
   363  		GasPrice:  Clone(tx.GetBody().GasPrice).([]byte),
   364  		Type:      tx.GetBody().Type,
   365  		Sign:      Clone(tx.GetBody().Sign).([]byte),
   366  	}
   367  	res := &transaction{
   368  		Tx: &Tx{Body: body},
   369  	}
   370  	res.Tx.Hash = res.CalculateTxHash()
   371  	return res
   372  }
   373  
   374  func (tx *transaction) GetMaxFee() *big.Int {
   375  	return fee.MaxPayloadTxFee(len(tx.GetBody().GetPayload()))
   376  }
   377  
   378  const allowedNameChar = "abcdefghijklmnopqrstuvwxyz1234567890"
   379  
   380  func validateAllowedChar(param []byte) error {
   381  	if param == nil {
   382  		return fmt.Errorf("invalid parameter in NameTx")
   383  	}
   384  	for _, char := range string(param) {
   385  		if !strings.Contains(allowedNameChar, strings.ToLower(string(char))) {
   386  			return fmt.Errorf("not allowed character in %s", string(param))
   387  		}
   388  	}
   389  	return nil
   390  }