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 }