github.com/iotexproject/iotex-core@v1.14.1-rc1/state/account.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package state 7 8 import ( 9 "math/big" 10 11 "github.com/iotexproject/go-pkgs/hash" 12 "github.com/pkg/errors" 13 "google.golang.org/protobuf/proto" 14 15 "github.com/iotexproject/iotex-core/action/protocol/account/accountpb" 16 ) 17 18 var ( 19 // ErrNotEnoughBalance is the error that the balance is not enough 20 ErrNotEnoughBalance = errors.New("not enough balance") 21 // ErrInvalidAmount is the error that the amount to add is negative 22 ErrInvalidAmount = errors.New("invalid amount") 23 // ErrAccountCollision is the error that the account already exists 24 ErrAccountCollision = errors.New("account already exists") 25 // ErrInvalidNonce is the error that the nonce to set is invalid 26 ErrInvalidNonce = errors.New("invalid nonce") 27 // ErrUnknownAccountType is the error that the account type is unknown 28 ErrUnknownAccountType = errors.New("unknown account type") 29 // ErrNonceOverflow is the error that the nonce overflow 30 ErrNonceOverflow = errors.New("nonce overflow") 31 ) 32 33 // LegacyNonceAccountTypeOption is an option to create account with new account type 34 func LegacyNonceAccountTypeOption() AccountCreationOption { 35 return func(account *Account) error { 36 account.accountType = 0 37 return nil 38 } 39 } 40 41 // DelegateCandidateOption is an option to create a delegate candidate account 42 func DelegateCandidateOption() AccountCreationOption { 43 return func(account *Account) error { 44 account.isCandidate = true 45 return nil 46 } 47 } 48 49 type ( 50 // AccountCreationOption is to create new account with specific settings 51 AccountCreationOption func(*Account) error 52 53 // Account is the canonical representation of an account. 54 Account struct { 55 // for Type 0, nonce 0 is reserved from actions in genesis block and coinbase transfers nonces 56 // other actions' nonces start from 1 57 nonce uint64 58 Balance *big.Int 59 Root hash.Hash256 // storage trie root for contract account 60 CodeHash []byte // hash of the smart contract byte-code for contract account 61 isCandidate bool 62 votingWeight *big.Int 63 accountType int32 64 } 65 ) 66 67 // ToProto converts to protobuf's Account 68 func (st *Account) ToProto() *accountpb.Account { 69 acPb := &accountpb.Account{} 70 acPb.Nonce = st.nonce 71 if _, ok := accountpb.AccountType_name[st.accountType]; !ok { 72 panic("unknown account type") 73 } 74 acPb.Type = accountpb.AccountType(st.accountType) 75 if st.Balance != nil { 76 acPb.Balance = st.Balance.String() 77 } 78 acPb.Root = make([]byte, len(st.Root)) 79 copy(acPb.Root, st.Root[:]) 80 acPb.CodeHash = make([]byte, len(st.CodeHash)) 81 copy(acPb.CodeHash, st.CodeHash) 82 acPb.IsCandidate = st.isCandidate 83 if st.votingWeight != nil { 84 acPb.VotingWeight = st.votingWeight.Bytes() 85 } 86 return acPb 87 } 88 89 // Serialize serializes account state into bytes 90 func (st Account) Serialize() ([]byte, error) { 91 return proto.Marshal(st.ToProto()) 92 } 93 94 // FromProto converts from protobuf's Account 95 func (st *Account) FromProto(acPb *accountpb.Account) { 96 st.nonce = acPb.Nonce 97 if _, ok := accountpb.AccountType_name[int32(acPb.Type.Number())]; !ok { 98 panic("unknown account type") 99 } 100 st.accountType = int32(acPb.Type.Number()) 101 if acPb.Balance == "" { 102 st.Balance = big.NewInt(0) 103 } else { 104 balance, ok := new(big.Int).SetString(acPb.Balance, 10) 105 if !ok { 106 panic(errors.Errorf("invalid balance %s", acPb.Balance)) 107 } 108 st.Balance = balance 109 } 110 copy(st.Root[:], acPb.Root) 111 st.CodeHash = nil 112 if acPb.CodeHash != nil { 113 st.CodeHash = make([]byte, len(acPb.CodeHash)) 114 copy(st.CodeHash, acPb.CodeHash) 115 } 116 st.isCandidate = acPb.IsCandidate 117 st.votingWeight = big.NewInt(0) 118 if acPb.VotingWeight != nil { 119 st.votingWeight.SetBytes(acPb.VotingWeight) 120 } 121 } 122 123 // Deserialize deserializes bytes into account state 124 func (st *Account) Deserialize(buf []byte) error { 125 acPb := &accountpb.Account{} 126 if err := proto.Unmarshal(buf, acPb); err != nil { 127 return err 128 } 129 st.FromProto(acPb) 130 return nil 131 } 132 133 // IsNewbieAccount returns true if the account has not sent any actions 134 func (st *Account) IsNewbieAccount() bool { 135 return st.nonce == 0 136 } 137 138 // IsLegacyFreshAccount returns true if a legacy account has not sent any actions 139 func (st *Account) IsLegacyFreshAccount() bool { 140 return st.accountType == 0 && st.nonce == 0 141 } 142 143 // AccountType returns the account type 144 func (st *Account) AccountType() int32 { 145 return st.accountType 146 } 147 148 // SetPendingNonce sets the pending nonce 149 func (st *Account) SetPendingNonce(nonce uint64) error { 150 // this is a legacy account that had never initiated an outgoing transaction 151 // so we can convert it to zero-nonce account 152 if st.accountType == 0 && st.nonce == 0 && nonce == 1 { 153 st.accountType = 1 154 } 155 156 switch st.accountType { 157 case 1: 158 if st.nonce+1 < st.nonce { 159 return errors.Wrapf(ErrNonceOverflow, "current value %d", st.nonce) 160 } 161 if nonce != st.nonce+1 { 162 return errors.Wrapf(ErrInvalidNonce, "actual value %d, %d expected", nonce, st.nonce+1) 163 } 164 st.nonce++ 165 case 0: 166 if st.nonce+2 < st.nonce { 167 return errors.Wrapf(ErrNonceOverflow, "current value %d", st.nonce) 168 } 169 if nonce != st.nonce+2 { 170 return errors.Wrapf(ErrInvalidNonce, "actual value %d, %d expected", nonce, st.nonce+2) 171 } 172 st.nonce++ 173 default: 174 return errors.Wrapf(ErrUnknownAccountType, "account type %d", st.accountType) 175 } 176 177 return nil 178 } 179 180 // ConvertFreshAccountToZeroNonceType converts a fresh legacy account to zero-nonce account 181 func (st *Account) ConvertFreshAccountToZeroNonceType(nonce uint64) bool { 182 if st.accountType == 0 && st.nonce == 0 && nonce == 0 { 183 // this is a legacy account that had never initiated an outgoing transaction 184 // so we can convert it to zero-nonce account 185 st.accountType = 1 186 return true 187 } 188 return false 189 } 190 191 // PendingNonce returns the pending nonce of the account 192 func (st *Account) PendingNonce() uint64 { 193 switch st.accountType { 194 case 1: 195 return st.nonce 196 case 0: 197 return st.nonce + 1 198 default: 199 panic(errors.Wrapf(ErrUnknownAccountType, "account type %d", st.accountType)) 200 } 201 } 202 203 // PendingNonceConsideringFreshAccount return the pending nonce considering fresh legacy account 204 func (st *Account) PendingNonceConsideringFreshAccount() uint64 { 205 if st.accountType == 0 && st.nonce == 0 { 206 // this is a legacy account that had never initiated an outgoing transaction 207 // so we can use 0 as the nonce for its very first transaction 208 return 0 209 } 210 return st.PendingNonce() 211 } 212 213 // MarkAsCandidate marks the account as a candidate 214 func (st *Account) MarkAsCandidate() { 215 st.isCandidate = true 216 } 217 218 // HasSufficientBalance returns true if balance is larger than amount 219 func (st *Account) HasSufficientBalance(amount *big.Int) bool { 220 if amount == nil { 221 return true 222 } 223 return amount.Cmp(st.Balance) <= 0 224 } 225 226 // AddBalance adds balance for account state 227 func (st *Account) AddBalance(amount *big.Int) error { 228 if amount == nil || amount.Sign() < 0 { 229 return errors.Wrapf(ErrInvalidAmount, "amount %s shouldn't be negative", amount.String()) 230 } 231 if st.Balance != nil { 232 st.Balance.Add(st.Balance, amount) 233 } else { 234 st.Balance = new(big.Int).Set(amount) 235 } 236 return nil 237 } 238 239 // SubBalance subtracts balance for account state 240 func (st *Account) SubBalance(amount *big.Int) error { 241 if amount == nil || amount.Cmp(big.NewInt(0)) < 0 { 242 return errors.Wrapf(ErrInvalidAmount, "amount %s shouldn't be negative", amount.String()) 243 } 244 // make sure there's enough fund to spend 245 if amount.Cmp(st.Balance) == 1 { 246 return ErrNotEnoughBalance 247 } 248 st.Balance.Sub(st.Balance, amount) 249 return nil 250 } 251 252 // IsContract returns true for contract account 253 func (st *Account) IsContract() bool { 254 return len(st.CodeHash) > 0 255 } 256 257 // Clone clones the account state 258 func (st *Account) Clone() *Account { 259 s := *st 260 s.Balance = nil 261 s.Balance = new(big.Int).Set(st.Balance) 262 s.votingWeight = nil 263 if st.votingWeight != nil { 264 s.votingWeight = new(big.Int).Set(st.votingWeight) 265 } 266 if st.CodeHash != nil { 267 s.CodeHash = nil 268 s.CodeHash = make([]byte, len(st.CodeHash)) 269 copy(s.CodeHash, st.CodeHash) 270 } 271 return &s 272 } 273 274 // NewAccount creates a new account with options 275 func NewAccount(opts ...AccountCreationOption) (*Account, error) { 276 account := &Account{ 277 Balance: big.NewInt(0), 278 votingWeight: big.NewInt(0), 279 accountType: 1, 280 } 281 for _, opt := range opts { 282 if err := opt(account); err != nil { 283 return nil, errors.Wrap(err, "failed to apply account creation option") 284 } 285 } 286 return account, nil 287 }