Fix errors/ and x/coin/errors.go

This commit is contained in:
Jae Kwon 2017-12-21 03:26:40 -08:00
parent a9b2636439
commit 6b5f08e918
8 changed files with 235 additions and 168 deletions

View File

@ -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 &copy
}
// 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
}

View File

@ -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) }

13
x/coin/decorator.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -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
}

6
x/coin/types.go Normal file
View File

@ -0,0 +1,6 @@
package coin
type Coinser interface {
GetCoins() Coins
SetCoins(Coins)
}

66
x/coin/utils.go Normal file
View File

@ -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
}