From 6b5f08e918eb8a3a3191973c7688339cc29c50fe Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 21 Dec 2017 03:26:40 -0800 Subject: [PATCH] Fix errors/ and x/coin/errors.go --- errors/errors.go | 123 ++++++++++++++++++++++++++++++-------------- x/coin/coin.go | 59 ++------------------- x/coin/decorator.go | 13 +++++ x/coin/errors.go | 69 ++++++++++++++++--------- x/coin/store.go | 20 ------- x/coin/tx.go | 47 ++++++----------- x/coin/types.go | 6 +++ x/coin/utils.go | 66 ++++++++++++++++++++++++ 8 files changed, 235 insertions(+), 168 deletions(-) create mode 100644 x/coin/decorator.go delete mode 100644 x/coin/store.go create mode 100644 x/coin/types.go create mode 100644 x/coin/utils.go diff --git a/errors/errors.go b/errors/errors.go index eca3cf618..8f6113f60 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -8,6 +8,7 @@ import ( const ( // ABCI Response Codes + // Base SDK reserves 0 ~ 99. CodeInternalError uint32 = 1 CodeTxParseError = 2 CodeBadNonce = 3 @@ -17,7 +18,7 @@ const ( ) // NOTE: Don't stringer this, we'll put better messages in later. -func codeToDefaultLog(code uint32) string { +func CodeToDefaultLog(code uint32) string { switch code { case CodeInternalError: return "Internal error" @@ -40,39 +41,43 @@ func codeToDefaultLog(code uint32) string { // All errors are created via constructors so as to enable us to hijack them // and inject stack traces if we really want to. -func InternalError(log string) sdkError { +func InternalError(log string) *sdkError { return newSDKError(CodeInternalError, log) } -func TxParseError(log string) sdkError { +func TxParseError(log string) *sdkError { return newSDKError(CodeTxParseError, log) } -func BadNonce(log string) sdkError { +func BadNonce(log string) *sdkError { return newSDKError(CodeBadNonce, log) } -func Unauthorized(log string) sdkError { +func Unauthorized(log string) *sdkError { return newSDKError(CodeUnauthorized, log) } -func InsufficientFunds(log string) sdkError { +func InsufficientFunds(log string) *sdkError { return newSDKError(CodeInsufficientFunds, log) } -func UnknownRequest(log string) sdkError { +func UnknownRequest(log string) *sdkError { return newSDKError(CodeUnknownRequest, log) } //---------------------------------------- +// ABCIError & sdkError type ABCIError interface { ABCICode() uint32 ABCILog() string - Error() string } +func NewABCIError(code uint32, log string) ABCIError { + return newSDKError(code, log) +} + /* This struct is intended to be used with pkg/errors. @@ -101,61 +106,103 @@ type sdkError struct { code uint32 log string cause error - // TODO stacktrace + // TODO stacktrace, optional. } -func newSDKError(code uint32, log string) sdkError { - // TODO capture stacktrace if ENV is set +func newSDKError(code uint32, log string) *sdkError { + // TODO capture stacktrace if ENV is set. if log == "" { - log = codeToDefaultLog(code) + log = CodeToDefaultLog(code) } - return sdkError{ - code: code, - log: log, + return &sdkError{ + code: code, + log: log, + cause: nil, } } -func (err sdkError) Error() string { +// Implements ABCIError +func (err *sdkError) Error() string { return fmt.Sprintf("SDKError{%d: %s}", err.code, err.log) } // Implements ABCIError -func (err sdkError) ABCICode() uint32 { +func (err *sdkError) ABCICode() uint32 { return err.code } // Implements ABCIError -func (err sdkError) ABCILog() string { +func (err *sdkError) ABCILog() string { return err.log } -func (err sdkError) Cause() error { - return err.cause +// Implements pkg/errors.causer +func (err *sdkError) Cause() error { + if err.cause != nil { + return err.cause + } + return err } -func (err sdkError) WithCause(cause error) sdkError { - copy := err +// Creates a cloned *sdkError with specific cause +func (err *sdkError) WithCause(cause error) *sdkError { + copy := *err copy.cause = cause - return copy + return © } -// HasErrorCode checks if this error would return the named error code -func HasErrorCode(err error, code uint32) bool { - // XXX Get the cause if not ABCIError - if abciErr, ok := err.(ABCIError); ok { - return abciErr.ABCICode() == code +//---------------------------------------- + +// HasSameCause returns true if both errors +// have the same cause. +func HasSameCause(err1 error, err2 error) bool { + if err1 != nil || err2 != nil { + panic("HasSomeCause() requires non-nil arguments") } - return code == CodeInternalError + return Cause(err1) == Cause(err2) } -func IsSameError(pattern error, err error) bool { - return err != nil && (errors.Cause(err) == errors.Cause(pattern)) -} - -func WithCode(err error, code uint32) sdkError { - return sdkError{ - code: code, - cause: err, - log: "", +// Like Cause but stops upon finding an ABCIError. +// If no error in the cause chain is an ABCIError, +// returns nil. +func ABCIErrorCause(err error) ABCIError { + for err != nil { + abciErr, ok := err.(ABCIError) + if ok { + return abciErr + } + cause, ok := err.(causer) + if !ok { + return nil + } + errCause := cause.Cause() + if errCause == nil || errCause == err { + return err + } + err = errCause } + return err +} + +// Identitical to pkg/errors.Cause, except handles .Cause() +// returning itself. +// TODO: Merge https://github.com/pkg/errors/issues/89 and +// delete this. +func Cause(err error) error { + for err != nil { + cause, ok := err.(causer) + if !ok { + return err + } + errCause := cause.Cause() + if errCause == nil || errCause == err { + return err + } + err = errCause + } + return err +} + +type causer interface { + Cause() error } diff --git a/x/coin/coin.go b/x/coin/coin.go index 168b06925..7c3af50d7 100644 --- a/x/coin/coin.go +++ b/x/coin/coin.go @@ -2,7 +2,6 @@ package coin import ( "fmt" - "regexp" "sort" "strconv" "strings" @@ -33,33 +32,8 @@ func (coin Coin) IsGTE(other Coin) bool { (coin.Amount >= other.Amount) } -//regex codes for extracting coins from string -var reDenom = regexp.MustCompile("") -var reAmt = regexp.MustCompile("(\\d+)") - -var reCoin = regexp.MustCompile("^([[:digit:]]+)[[:space:]]*([[:alpha:]]+)$") - -// ParseCoin parses a cli input for one coin type, returning errors if invalid. -// This returns an error on an empty string as well. -func ParseCoin(str string) (Coin, error) { - var coin Coin - - matches := reCoin.FindStringSubmatch(strings.TrimSpace(str)) - if matches == nil { - return coin, errors.Errorf("%s is invalid coin definition", str) - } - - // parse the amount (should always parse properly) - amt, err := strconv.Atoi(matches[1]) - if err != nil { - return coin, err - } - - coin = Coin{matches[2], int64(amt)} - return coin, nil -} - //---------------------------------------- +// Coins // Coins is a set of Coin, one per currency type Coins []Coin @@ -76,34 +50,6 @@ func (coins Coins) String() string { return out[:len(out)-1] } -// ParseCoins will parse out a list of coins separated by commas. -// If nothing is provided, it returns an empty array -func ParseCoins(str string) (Coins, error) { - // empty string is empty list... - if len(str) == 0 { - return nil, nil - } - - split := strings.Split(str, ",") - var coins Coins - - for _, el := range split { - coin, err := ParseCoin(el) - if err != nil { - return coins, err - } - coins = append(coins, coin) - } - - // ensure they are in proper order, to avoid random failures later - coins.Sort() - if !coins.IsValid() { - return nil, errors.Errorf("ParseCoins invalid: %#v", coins) - } - - return coins, nil -} - // IsValid asserts the Coins are sorted, and don't have 0 amounts func (coins Coins) IsValid() bool { switch len(coins) { @@ -242,7 +188,8 @@ func (coins Coins) IsNonnegative() bool { return true } -/*** Implement Sort interface ***/ +//---------------------------------------- +// Sort interface //nolint func (coins Coins) Len() int { return len(coins) } diff --git a/x/coin/decorator.go b/x/coin/decorator.go new file mode 100644 index 000000000..2ef0df008 --- /dev/null +++ b/x/coin/decorator.go @@ -0,0 +1,13 @@ +package coin + +import ( + sdk "github.com/cosmos/cosmos-sdk" +) + +func Decorator(ctx sdk.Context, store sdk.MultiStore, tx sdk.Tx, next sdk.Handler) sdk.Result { + if msg, ok := tx.(CoinsMsg); ok { + handleCoinsMsg(ctx, store, msg) + } else { + next(ctx, store, tx) + } +} diff --git a/x/coin/errors.go b/x/coin/errors.go index e53f26256..f5419d9c9 100644 --- a/x/coin/errors.go +++ b/x/coin/errors.go @@ -7,47 +7,68 @@ import ( "github.com/cosmos/cosmos-sdk/errors" ) -var ( - errNoAccount = fmt.Errorf("No such account") - errInsufficientFunds = fmt.Errorf("Insufficient funds") - errInsufficientCredit = fmt.Errorf("Insufficient credit") - errNoInputs = fmt.Errorf("No input coins") - errNoOutputs = fmt.Errorf("No output coins") - errInvalidAddress = fmt.Errorf("Invalid address") - errInvalidCoins = fmt.Errorf("Invalid coins") -) - const ( + // Coin errors reserve 100 ~ 199. CodeInvalidInput uint32 = 101 CodeInvalidOutput uint32 = 102 + CodeInvalidAddress uint32 = 103 CodeUnknownAddress uint32 = 103 CodeUnknownRequest uint32 = errors.CodeUnknownRequest ) -func ErrNoAccount() errors.ABCIError { - return errors.WithCode(errNoAccount, CodeUnknownAddress) +// NOTE: Don't stringer this, we'll put better messages in later. +func codeToDefaultLog(code uint32) string { + switch code { + case CodeInvalidInput: + return "Invalid input coins" + case CodeInvalidOutput: + return "Invalid output coins" + case CodeInvalidAddress: + return "Invalid address" + case CodeUnknownAddress: + return "Unknown address" + case CodeUnknownRequest: + return "Unknown request" + default: + return errors.CodeToDefaultLog(code) + } } -func ErrInvalidAddress() errors.ABCIError { - return errors.WithCode(errInvalidAddress, CodeInvalidInput) +//---------------------------------------- +// Error constructors + +func ErrInvalidInput(log string) error { + return newError(CodeInvalidInput, log) } -func ErrInvalidCoins() errors.ABCIError { - return errors.WithCode(errInvalidCoins, CodeInvalidInput) +func ErrInvalidOutput(log string) error { + return newError(CodeInvalidOutput, log) } -func ErrInsufficientFunds() errors.ABCIError { - return errors.WithCode(errInsufficientFunds, CodeInvalidInput) +func ErrInvalidAddress(log string) error { + return newError(CodeInvalidAddress, log) } -func ErrInsufficientCredit() errors.ABCIError { - return errors.WithCode(errInsufficientCredit, CodeInvalidInput) +func ErrUnknownAddress(log string) error { + return newError(CodeUnknownAddress, log) } -func ErrNoInputs() errors.ABCIError { - return errors.WithCode(errNoInputs, CodeInvalidInput) +func ErrUnknownRequest(log string) error { + return newError(CodeUnknownRequest, log) } -func ErrNoOutputs() errors.ABCIError { - return errors.WithCode(errNoOutputs, CodeInvalidOutput) +//---------------------------------------- +// Misc + +func logOrDefault(log string, code uint32) string { + if log != "" { + return log + } else { + return codeToDefaultLog + } +} + +func newError(code uint32, log string) error { + log = logOrDefaultLog(log, code) + return errors.NewABCIError(code, log) } diff --git a/x/coin/store.go b/x/coin/store.go deleted file mode 100644 index 8a8dc9e44..000000000 --- a/x/coin/store.go +++ /dev/null @@ -1,20 +0,0 @@ -package coin - -import "github.com/tendermint/go-wire/data" - -// TEMP - -type Actor struct { - ChainID string - App string - Address data.Bytes -} - -// Account - coin account structure -type Account struct { - // Coins is how much is on the account - Coins Coins `json:"coins"` - // Credit is how much has been "fronted" to the account - // (this is usually 0 except for trusted chains) - Credit Coins `json:"credit"` -} diff --git a/x/coin/tx.go b/x/coin/tx.go index 73c10f687..fcb0c84d5 100644 --- a/x/coin/tx.go +++ b/x/coin/tx.go @@ -1,47 +1,34 @@ package coin +// TODO rename this to msg.go + import ( "fmt" + cmn "github.com/tendermint/tmlibs/common" ) -/*func init() { - sdk.TxMapper. - RegisterImplementation(SendTx{}, TypeSend, ByteSend). - RegisterImplementation(CreditTx{}, TypeCredit, ByteCredit) -}*/ - -// we reserve the 0x20-0x3f range for standard modules -const ( - NameCoin = "coin" - - ByteSend = 0x20 - TypeSend = NameCoin + "/send" - ByteCredit = 0x21 - TypeCredit = NameCoin + "/credit" -) +type CoinMsg interface { + AssertIsCoinMsg() + Type() string // "send", "credit" +} //----------------------------------------------------------------------------- -// TxInput - expected coin movement outputs, used with SendTx -type TxInput struct { - Address Actor `json:"address"` - Coins Coins `json:"coins"` +// Input is a source of coins in a transaction. +type Input struct { + Address cmn.Bytes + Coins Coins } -// ValidateBasic - validate transaction input -func (txIn TxInput) ValidateBasic() error { - if txIn.Address.App == "" { +func (in Input) ValidateBasic() error { + if !auth.IsValidAddress(in.Address) { return ErrInvalidAddress() } - // TODO: knowledge of app-specific codings? - if len(txIn.Address.Address) == 0 { - return ErrInvalidAddress() + if !in.Coins.IsValid() { + return ErrInvalidInput() } - if !txIn.Coins.IsValid() { - return ErrInvalidCoins() - } - if !txIn.Coins.IsPositive() { - return ErrInvalidCoins() + if !in.Coins.IsPositive() { + return ErrInvalidInput() } return nil } diff --git a/x/coin/types.go b/x/coin/types.go new file mode 100644 index 000000000..5da29b465 --- /dev/null +++ b/x/coin/types.go @@ -0,0 +1,6 @@ +package coin + +type Coinser interface { + GetCoins() Coins + SetCoins(Coins) +} diff --git a/x/coin/utils.go b/x/coin/utils.go new file mode 100644 index 000000000..7cbb54abf --- /dev/null +++ b/x/coin/utils.go @@ -0,0 +1,66 @@ +package coin + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +var ( + // Denominations can be 3 ~ 16 characters long. + rDnm = `[[:alpha:]][[:alnum:]]{2,15}` + rAmt = `[[:digit:]]+` + rSpc = `[[:space:]]*` + reCoin_ = fmt.Sprintf(`^(%s)%s(%s)$`, reDenom_, re_, reAmt_) +) + +// ParseCoin parses a cli input for one coin type, returning errors if invalid. +// This returns an error on an empty string as well. +func ParseCoin(coinStr string) (coin Coin, err error) { + coinStr = strings.TrimSpace(coinStr) + + matches := reCoin.FindStringSubmatch(coinStr) + if matches == nil { + err = errors.Errorf("Invalid coin expression: %s", coinStr) + return + } + denomStr, amountStr := matches[2], matches[1] + + amount, err := strconv.Atoi(amountStr) + if err != nil { + return + } + + return Coin{denomStr, int64(amount)}, nil +} + +// ParseCoins will parse out a list of coins separated by commas. +// If nothing is provided, it returns nil Coins. +// Returned coins are sorted. +func ParseCoins(coinsStr string) (coins Coins, err error) { + coinsStr = strings.TrimSpace(coinsStr) + if len(coinsStr) == 0 { + return nil, nil + } + + coinStrs := strings.Split(coinsStr, ",") + for _, coinStr := range coinStrs { + coin, err := ParseCoin(coinStr) + if err != nil { + return nil, err + } + coins = append(coins, coin) + } + + // Sort coins for determinism. + coins.Sort() + + // Validate coins before returning. + if !coins.IsValid() { + return nil, errors.Errorf("ParseCoins invalid: %#v", coins) + } + + return coins, nil +}