OSDN Git Service

reorganize error code (#1133)
authoroysheng <33340252+oysheng@users.noreply.github.com>
Thu, 19 Jul 2018 11:06:41 +0000 (19:06 +0800)
committerPaladz <yzhu101@uottawa.ca>
Thu, 19 Jul 2018 11:06:41 +0000 (19:06 +0800)
* modify errors for build-transaction

* submit transaction error

* modify onlyHaveSpendActions to onlyHaveInputActions
add build-transaction error code

* modify validation error

* add vm error

* add hsm error

* modify run VM error

* modify rum VM wrapErr function

api/errors.go
api/request.go
api/transact.go
blockchain/txbuilder/finalize.go
protocol/validation/block.go
protocol/validation/block_test.go
protocol/validation/tx.go
protocol/validation/tx_test.go
protocol/vm/errors.go
protocol/vm/vm.go

index d18083e..2e24c1f 100644 (file)
@@ -3,6 +3,8 @@ package api
 import (
        "context"
 
+       "github.com/bytom/account"
+       "github.com/bytom/asset"
        "github.com/bytom/blockchain/pseudohsm"
        "github.com/bytom/blockchain/rpc"
        "github.com/bytom/blockchain/signers"
@@ -11,6 +13,8 @@ import (
        "github.com/bytom/net/http/httperror"
        "github.com/bytom/net/http/httpjson"
        "github.com/bytom/protocol"
+       "github.com/bytom/protocol/validation"
+       "github.com/bytom/protocol/vm"
 )
 
 func isTemporary(info httperror.Info, err error) bool {
@@ -42,15 +46,65 @@ var respErrFormatter = map[error]httperror.Info{
        signers.ErrDupeXPub:  {400, "BTM204", "Root XPubs cannot contain the same key more than once"},
 
        // Transaction error namespace (7xx)
-       // Build error namespace (70x)
-       txbuilder.ErrBadAmount: {400, "BTM704", "Invalid asset amount"},
+       // Build transaction error namespace (70x ~ 72x)
+       account.ErrInsufficient:    {400, "BTM700", "Funds of account are insufficient"},
+       account.ErrImmature:        {400, "BTM701", "Available funds of account are immature"},
+       account.ErrReserved:        {400, "BTM702", "Available UTXOs of account have been reserved"},
+       account.ErrMatchUTXO:       {400, "BTM703", "Not found UTXO with given hash"},
+       ErrBadActionType:           {400, "BTM704", "Invalid action type"},
+       ErrBadAction:               {400, "BTM705", "Invalid action object"},
+       ErrBadActionConstruction:   {400, "BTM706", "Invalid action construction"},
+       txbuilder.ErrMissingFields: {400, "BTM707", "One or more fields are missing"},
+       txbuilder.ErrBadAmount:     {400, "BTM708", "Invalid asset amount"},
+       account.ErrFindAccount:     {400, "BTM709", "Not found account"},
+       asset.ErrFindAsset:         {400, "BTM710", "Not found asset"},
 
-       //Error code 050 represents alias of key duplicated
-       pseudohsm.ErrDuplicateKeyAlias: {400, "BTM050", "Alias already exists"},
-       //Error code 801 represents query request format error
-       pseudohsm.ErrInvalidAfter: httperror.Info{400, "BTM801", "Invalid `after` in query"},
-       //Error code 802 represents query reponses too many
-       pseudohsm.ErrTooManyAliasesToList: {400, "BTM802", "Too many aliases to list"},
+       // Submit transaction error namespace (73x ~ 79x)
+       // Validation error (73x ~ 75x)
+       validation.ErrTxVersion:                 {400, "BTM730", "Invalid transaction version"},
+       validation.ErrWrongTransactionSize:      {400, "BTM731", "Invalid transaction size"},
+       validation.ErrBadTimeRange:              {400, "BTM732", "Invalid transaction time range"},
+       validation.ErrNotStandardTx:             {400, "BTM733", "Not standard transaction"},
+       validation.ErrWrongCoinbaseTransaction:  {400, "BTM734", "Invalid coinbase transaction"},
+       validation.ErrWrongCoinbaseAsset:        {400, "BTM735", "Invalid coinbase assetID"},
+       validation.ErrCoinbaseArbitraryOversize: {400, "BTM736", "Invalid coinbase arbitrary size"},
+       validation.ErrEmptyResults:              {400, "BTM737", "No results in the transaction"},
+       validation.ErrMismatchedAssetID:         {400, "BTM738", "Mismatched assetID"},
+       validation.ErrMismatchedPosition:        {400, "BTM739", "Mismatched value source/dest position"},
+       validation.ErrMismatchedReference:       {400, "BTM740", "Mismatched reference"},
+       validation.ErrMismatchedValue:           {400, "BTM741", "Mismatched value"},
+       validation.ErrMissingField:              {400, "BTM742", "Missing required field"},
+       validation.ErrNoSource:                  {400, "BTM743", "No source for value"},
+       validation.ErrOverflow:                  {400, "BTM744", "Arithmetic overflow/underflow"},
+       validation.ErrPosition:                  {400, "BTM745", "Invalid source or destination position"},
+       validation.ErrUnbalanced:                {400, "BTM746", "Unbalanced asset amount between input and output"},
+       validation.ErrOverGasCredit:             {400, "BTM747", "Gas credit has been spent"},
+       validation.ErrGasCalculate:              {400, "BTM748", "Gas usage calculate got a math error"},
+
+       // VM error (76x ~ 78x)
+       vm.ErrAltStackUnderflow:  {400, "BTM760", "Alt stack underflow"},
+       vm.ErrBadValue:           {400, "BTM761", "Bad value"},
+       vm.ErrContext:            {400, "BTM762", "Wrong context"},
+       vm.ErrDataStackUnderflow: {400, "BTM763", "Data stack underflow"},
+       vm.ErrDisallowedOpcode:   {400, "BTM764", "Disallowed opcode"},
+       vm.ErrDivZero:            {400, "BTM765", "Division by zero"},
+       vm.ErrFalseVMResult:      {400, "BTM766", "False result for executing VM"},
+       vm.ErrLongProgram:        {400, "BTM767", "Program size exceeds max int32"},
+       vm.ErrRange:              {400, "BTM768", "Arithmetic range error"},
+       vm.ErrReturn:             {400, "BTM769", "RETURN executed"},
+       vm.ErrRunLimitExceeded:   {400, "BTM770", "Run limit exceeded because the BTM Fee is insufficient"},
+       vm.ErrShortProgram:       {400, "BTM771", "Unexpected end of program"},
+       vm.ErrToken:              {400, "BTM772", "Unrecognized token"},
+       vm.ErrUnexpected:         {400, "BTM773", "Unexpected error"},
+       vm.ErrUnsupportedVM:      {400, "BTM774", "Unsupported VM because the version of VM is mismatched"},
+       vm.ErrVerifyFailed:       {400, "BTM775", "VERIFY failed"},
+
+       // Mock HSM error namespace (8xx)
+       pseudohsm.ErrDuplicateKeyAlias:    {400, "BTM800", "Key Alias already exists"},
+       pseudohsm.ErrInvalidAfter:         {400, "BTM801", "Invalid `after` in query"},
+       pseudohsm.ErrLoadKey:              {400, "BTM802", "Key not found or wrong password"},
+       pseudohsm.ErrTooManyAliasesToList: {400, "BTM803", "Requested key aliases exceeds limit"},
+       pseudohsm.ErrDecrypt:              {400, "BTM804", "Could not decrypt key with given passphrase"},
 }
 
 // Map error values to standard bytom error codes. Missing entries
@@ -64,10 +118,9 @@ var errorFormatter = httperror.Formatter{
        Errors: map[error]httperror.Info{
                // General error namespace (0xx)
                context.DeadlineExceeded:     {408, "BTM001", "Request timed out"},
-               httpjson.ErrBadRequest:       {400, "BTM003", "Invalid request body"},
-               txbuilder.ErrMissingFields:   {400, "BTM010", "One or more fields are missing"},
-               rpc.ErrWrongNetwork:          {502, "BTM104", "A peer core is operating on a different blockchain network"},
-               protocol.ErrTheDistantFuture: {400, "BTM105", "Requested height is too far ahead"},
+               httpjson.ErrBadRequest:       {400, "BTM002", "Invalid request body"},
+               rpc.ErrWrongNetwork:          {502, "BTM103", "A peer core is operating on a different blockchain network"},
+               protocol.ErrTheDistantFuture: {400, "BTM104", "Requested height is too far ahead"},
 
                //accesstoken authz err namespace (86x)
                errNotAuthenticated: {401, "BTM860", "Request could not be authenticated"},
index f40ef46..20c6297 100644 (file)
@@ -10,11 +10,11 @@ import (
        "github.com/bytom/protocol/bc/types"
 )
 
+// action error
 var (
-       errBadActionType = errors.New("bad action type")
-       errBadAction     = errors.New("bad action object")
-       errEmptyAmount   = errors.New("nil amount in the request actions")
-       errBadAmount     = errors.New("bad amount in the request actions")
+       ErrBadActionType         = errors.New("bad action type")
+       ErrBadAction             = errors.New("bad action object")
+       ErrBadActionConstruction = errors.New("bad action construction")
 )
 
 // BuildRequest is main struct when building transactions
@@ -25,19 +25,19 @@ type BuildRequest struct {
        TimeRange uint64                   `json:"time_range"`
 }
 
-func (a *API) completeMissingIds(ctx context.Context, br *BuildRequest) error {
+func (a *API) completeMissingIDs(ctx context.Context, br *BuildRequest) error {
        for i, m := range br.Actions {
-               if err := a.completeMissingAssetId(m, i); err != nil {
+               if err := a.completeMissingAssetID(m, i); err != nil {
                        return err
                }
-               if err := a.completeMissingAccountId(m, i, ctx); err != nil {
+               if err := a.completeMissingAccountID(m, i, ctx); err != nil {
                        return err
                }
        }
        return nil
 }
 
-func (a *API) completeMissingAssetId(m map[string]interface{}, index int) error {
+func (a *API) completeMissingAssetID(m map[string]interface{}, index int) error {
        id, _ := m["asset_id"].(string)
        alias, _ := m["asset_alias"].(string)
        if id == "" && alias != "" {
@@ -56,7 +56,7 @@ func (a *API) completeMissingAssetId(m map[string]interface{}, index int) error
        return nil
 }
 
-func (a *API) completeMissingAccountId(m map[string]interface{}, index int, ctx context.Context) error {
+func (a *API) completeMissingAccountID(m map[string]interface{}, index int, ctx context.Context) error {
        id, _ := m["account_id"].(string)
        alias, _ := m["account_alias"].(string)
        if id == "" && alias != "" {
index 8260db5..338a15c 100644 (file)
@@ -38,35 +38,42 @@ func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, erro
        return decoder, ok
 }
 
-func onlyHaveSpendActions(req *BuildRequest) bool {
+func onlyHaveInputActions(req *BuildRequest) (bool, error) {
        count := 0
-       for _, m := range req.Actions {
-               if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
+       for i, act := range req.Actions {
+               actionType, ok := act["type"].(string)
+               if !ok {
+                       return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
+               }
+
+               if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
                        count++
                }
        }
 
-       return count == len(req.Actions)
+       return count == len(req.Actions), nil
 }
 
 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
-       if err := a.completeMissingIds(ctx, req); err != nil {
+       if err := a.completeMissingIDs(ctx, req); err != nil {
                return nil, err
        }
 
-       if onlyHaveSpendActions(req) {
-               return nil, errors.New("transaction only contain spend actions, didn't have output actions")
+       if ok, err := onlyHaveInputActions(req); err != nil {
+               return nil, err
+       } else if ok {
+               return nil, errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
        }
 
        actions := make([]txbuilder.Action, 0, len(req.Actions))
        for i, act := range req.Actions {
                typ, ok := act["type"].(string)
                if !ok {
-                       return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
+                       return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
                }
                decoder, ok := a.actionDecoder(typ)
                if !ok {
-                       return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
+                       return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
                }
 
                // Remarshal to JSON, the action may have been modified when we
@@ -77,7 +84,7 @@ func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Te
                }
                action, err := decoder(b)
                if err != nil {
-                       return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
+                       return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
                }
                actions = append(actions, action)
        }
@@ -93,10 +100,14 @@ func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Te
        if errors.Root(err) == txbuilder.ErrAction {
                // append each of the inner errors contained in the data.
                var Errs string
-               for _, innerErr := range errors.Data(err)["actions"].([]error) {
-                       Errs = Errs + "<" + innerErr.Error() + ">"
+               var rootErr error
+               for i, innerErr := range errors.Data(err)["actions"].([]error) {
+                       if i == 0 {
+                               rootErr = errors.Root(innerErr)
+                       }
+                       Errs = Errs + innerErr.Error()
                }
-               err = errors.New(err.Error() + "-" + Errs)
+               err = errors.WithDetail(rootErr, Errs)
        }
        if err != nil {
                return nil, err
index 043954e..83ce4c7 100644 (file)
@@ -40,7 +40,7 @@ func FinalizeTx(ctx context.Context, c *protocol.Chain, tx *types.Tx) error {
                return errors.Sub(ErrRejected, err)
        }
        if err != nil {
-               return errors.Wrap(err, "tx rejected")
+               return errors.WithDetail(err, "tx rejected: "+err.Error())
        }
 
        return errors.Wrap(err)
index 54173c9..6df1e1e 100644 (file)
@@ -34,7 +34,7 @@ func checkBlockTime(b *bc.Block, parent *state.BlockNode) error {
 
 func checkCoinbaseAmount(b *bc.Block, amount uint64) error {
        if len(b.Transactions) == 0 {
-               return errors.Wrap(errWrongCoinbaseTransaction, "block is empty")
+               return errors.Wrap(ErrWrongCoinbaseTransaction, "block is empty")
        }
 
        tx := b.Transactions[0]
@@ -44,7 +44,7 @@ func checkCoinbaseAmount(b *bc.Block, amount uint64) error {
        }
 
        if output.Source.Value.Amount != amount {
-               return errors.Wrap(errWrongCoinbaseTransaction, "dismatch output amount")
+               return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output amount")
        }
        return nil
 }
index fede7b0..aac57d8 100644 (file)
@@ -72,12 +72,12 @@ func TestCheckCoinbaseAmount(t *testing.T) {
                                }),
                        },
                        amount: 6000,
-                       err:    errWrongCoinbaseTransaction,
+                       err:    ErrWrongCoinbaseTransaction,
                },
                {
                        txs:    []*types.Tx{},
                        amount: 5000,
-                       err:    errWrongCoinbaseTransaction,
+                       err:    ErrWrongCoinbaseTransaction,
                },
        }
 
index 65009e0..fe2a274 100644 (file)
@@ -12,8 +12,28 @@ import (
        "github.com/bytom/protocol/vm"
 )
 
-// timeRangeGash is the block height we will reach after 100 years
-const timeRangeGash = uint64(21024000)
+// validate transaction error
+var (
+       ErrTxVersion                 = errors.New("invalid transaction version")
+       ErrWrongTransactionSize      = errors.New("invalid transaction size")
+       ErrBadTimeRange              = errors.New("invalid transaction time range")
+       ErrNotStandardTx             = errors.New("not standard transaction")
+       ErrWrongCoinbaseTransaction  = errors.New("wrong coinbase transaction")
+       ErrWrongCoinbaseAsset        = errors.New("wrong coinbase assetID")
+       ErrCoinbaseArbitraryOversize = errors.New("coinbase arbitrary size is larger than limit")
+       ErrEmptyResults              = errors.New("transaction has no results")
+       ErrMismatchedAssetID         = errors.New("mismatched assetID")
+       ErrMismatchedPosition        = errors.New("mismatched value source/dest position")
+       ErrMismatchedReference       = errors.New("mismatched reference")
+       ErrMismatchedValue           = errors.New("mismatched value")
+       ErrMissingField              = errors.New("missing required field")
+       ErrNoSource                  = errors.New("no source for value")
+       ErrOverflow                  = errors.New("arithmetic overflow/underflow")
+       ErrPosition                  = errors.New("invalid source or destination position")
+       ErrUnbalanced                = errors.New("unbalanced asset amount between input and output")
+       ErrOverGasCredit             = errors.New("all gas credit has been spend")
+       ErrGasCalculate              = errors.New("gas usage calculate got a math error")
+)
 
 // GasState record the gas usage status
 type GasState struct {
@@ -26,14 +46,14 @@ type GasState struct {
 
 func (g *GasState) setGas(BTMValue int64, txSize int64) error {
        if BTMValue < 0 {
-               return errors.Wrap(errGasCalculate, "input BTM is negative")
+               return errors.Wrap(ErrGasCalculate, "input BTM is negative")
        }
 
        g.BTMValue = uint64(BTMValue)
 
        var ok bool
        if g.GasLeft, ok = checked.DivInt64(BTMValue, consensus.VMGasRate); !ok {
-               return errors.Wrap(errGasCalculate, "setGas calc gas amount")
+               return errors.Wrap(ErrGasCalculate, "setGas calc gas amount")
        }
 
        if g.GasLeft > consensus.MaxGasAmount {
@@ -41,7 +61,7 @@ func (g *GasState) setGas(BTMValue int64, txSize int64) error {
        }
 
        if g.StorageGas, ok = checked.MulInt64(txSize, consensus.StorageGasRate); !ok {
-               return errors.Wrap(errGasCalculate, "setGas calc tx storage gas")
+               return errors.Wrap(ErrGasCalculate, "setGas calc tx storage gas")
        }
        return nil
 }
@@ -49,11 +69,11 @@ func (g *GasState) setGas(BTMValue int64, txSize int64) error {
 func (g *GasState) setGasValid() error {
        var ok bool
        if g.GasLeft, ok = checked.SubInt64(g.GasLeft, g.StorageGas); !ok || g.GasLeft < 0 {
-               return errors.Wrap(errGasCalculate, "setGasValid calc gasLeft")
+               return errors.Wrap(ErrGasCalculate, "setGasValid calc gasLeft")
        }
 
        if g.GasUsed, ok = checked.AddInt64(g.GasUsed, g.StorageGas); !ok {
-               return errors.Wrap(errGasCalculate, "setGasValid calc gasUsed")
+               return errors.Wrap(ErrGasCalculate, "setGasValid calc gasUsed")
        }
 
        g.GasValid = true
@@ -62,18 +82,18 @@ func (g *GasState) setGasValid() error {
 
 func (g *GasState) updateUsage(gasLeft int64) error {
        if gasLeft < 0 {
-               return errors.Wrap(errGasCalculate, "updateUsage input negative gas")
+               return errors.Wrap(ErrGasCalculate, "updateUsage input negative gas")
        }
 
        if gasUsed, ok := checked.SubInt64(g.GasLeft, gasLeft); ok {
                g.GasUsed += gasUsed
                g.GasLeft = gasLeft
        } else {
-               return errors.Wrap(errGasCalculate, "updateUsage calc gas diff")
+               return errors.Wrap(ErrGasCalculate, "updateUsage calc gas diff")
        }
 
        if !g.GasValid && (g.GasUsed > consensus.DefaultGasCredit || g.StorageGas > g.GasLeft) {
-               return errOverGasCredit
+               return ErrOverGasCredit
        }
        return nil
 }
@@ -90,28 +110,6 @@ type validationState struct {
        cache     map[bc.Hash]error // Memoized per-entry validation results
 }
 
-var (
-       errBadTimeRange              = errors.New("tx time range is invalid")
-       errCoinbaseArbitraryOversize = errors.New("coinbase arbitrary size is larger than limit")
-       errGasCalculate              = errors.New("gas usage calculate got a math error")
-       errEmptyResults              = errors.New("transaction has no results")
-       errMismatchedAssetID         = errors.New("mismatched asset id")
-       errMismatchedPosition        = errors.New("mismatched value source/dest positions")
-       errMismatchedReference       = errors.New("mismatched reference")
-       errMismatchedValue           = errors.New("mismatched value")
-       errMissingField              = errors.New("missing required field")
-       errNoSource                  = errors.New("no source for value")
-       errOverflow                  = errors.New("arithmetic overflow/underflow")
-       errOverGasCredit             = errors.New("all gas credit has been spend")
-       errPosition                  = errors.New("invalid source or destination position")
-       errTxVersion                 = errors.New("invalid transaction version")
-       errUnbalanced                = errors.New("unbalanced")
-       errWrongTransactionSize      = errors.New("transaction size is not in valid range")
-       errWrongCoinbaseTransaction  = errors.New("wrong coinbase transaction")
-       errWrongCoinbaseAsset        = errors.New("wrong coinbase asset id")
-       errNotStandardTx             = errors.New("gas transaction is not standard transaction")
-)
-
 func checkValid(vs *validationState, e bc.Entry) (err error) {
        var ok bool
        entryID := bc.EntryID(e)
@@ -135,18 +133,18 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                }
 
                if e.Version == 1 && len(e.ResultIds) == 0 {
-                       return errEmptyResults
+                       return ErrEmptyResults
                }
 
        case *bc.Mux:
                parity := make(map[bc.AssetID]int64)
                for i, src := range e.Sources {
                        if src.Value.Amount > math.MaxInt64 {
-                               return errors.WithDetailf(errOverflow, "amount %d exceeds maximum value 2^63", src.Value.Amount)
+                               return errors.WithDetailf(ErrOverflow, "amount %d exceeds maximum value 2^63", src.Value.Amount)
                        }
                        sum, ok := checked.AddInt64(parity[*src.Value.AssetId], int64(src.Value.Amount))
                        if !ok {
-                               return errors.WithDetailf(errOverflow, "adding %d units of asset %x from mux source %d to total %d overflows int64", src.Value.Amount, src.Value.AssetId.Bytes(), i, parity[*src.Value.AssetId])
+                               return errors.WithDetailf(ErrOverflow, "adding %d units of asset %x from mux source %d to total %d overflows int64", src.Value.Amount, src.Value.AssetId.Bytes(), i, parity[*src.Value.AssetId])
                        }
                        parity[*src.Value.AssetId] = sum
                }
@@ -154,14 +152,14 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                for i, dest := range e.WitnessDestinations {
                        sum, ok := parity[*dest.Value.AssetId]
                        if !ok {
-                               return errors.WithDetailf(errNoSource, "mux destination %d, asset %x, has no corresponding source", i, dest.Value.AssetId.Bytes())
+                               return errors.WithDetailf(ErrNoSource, "mux destination %d, asset %x, has no corresponding source", i, dest.Value.AssetId.Bytes())
                        }
                        if dest.Value.Amount > math.MaxInt64 {
-                               return errors.WithDetailf(errOverflow, "amount %d exceeds maximum value 2^63", dest.Value.Amount)
+                               return errors.WithDetailf(ErrOverflow, "amount %d exceeds maximum value 2^63", dest.Value.Amount)
                        }
                        diff, ok := checked.SubInt64(sum, int64(dest.Value.Amount))
                        if !ok {
-                               return errors.WithDetailf(errOverflow, "subtracting %d units of asset %x from mux destination %d from total %d underflows int64", dest.Value.Amount, dest.Value.AssetId.Bytes(), i, sum)
+                               return errors.WithDetailf(ErrOverflow, "subtracting %d units of asset %x from mux destination %d from total %d underflows int64", dest.Value.Amount, dest.Value.AssetId.Bytes(), i, sum)
                        }
                        parity[*dest.Value.AssetId] = diff
                }
@@ -172,7 +170,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                                        return err
                                }
                        } else if amount != 0 {
-                               return errors.WithDetailf(errUnbalanced, "asset %x sources - destinations = %d (should be 0)", assetID.Bytes(), amount)
+                               return errors.WithDetailf(ErrUnbalanced, "asset %x sources - destinations = %d (should be 0)", assetID.Bytes(), amount)
                        }
                }
 
@@ -228,7 +226,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
        case *bc.Issuance:
                computedAssetID := e.WitnessAssetDefinition.ComputeAssetID()
                if computedAssetID != *e.Value.AssetId {
-                       return errors.WithDetailf(errMismatchedAssetID, "asset ID is %x, issuance wants %x", computedAssetID.Bytes(), e.Value.AssetId.Bytes())
+                       return errors.WithDetailf(ErrMismatchedAssetID, "asset ID is %x, issuance wants %x", computedAssetID.Bytes(), e.Value.AssetId.Bytes())
                }
 
                gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
@@ -247,7 +245,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
 
        case *bc.Spend:
                if e.SpentOutputId == nil {
-                       return errors.Wrap(errMissingField, "spend without spent output ID")
+                       return errors.Wrap(ErrMissingField, "spend without spent output ID")
                }
                spentOutput, err := vs.tx.Output(*e.SpentOutputId)
                if err != nil {
@@ -268,7 +266,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                }
                if !eq {
                        return errors.WithDetailf(
-                               errMismatchedValue,
+                               ErrMismatchedValue,
                                "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
                                spentOutput.Source.Value.Amount,
                                spentOutput.Source.Value.AssetId.Bytes(),
@@ -285,15 +283,15 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
 
        case *bc.Coinbase:
                if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
-                       return errWrongCoinbaseTransaction
+                       return ErrWrongCoinbaseTransaction
                }
 
                if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
-                       return errWrongCoinbaseAsset
+                       return ErrWrongCoinbaseAsset
                }
 
                if e.Arbitrary != nil && len(e.Arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
-                       return errCoinbaseArbitraryOversize
+                       return ErrCoinbaseArbitraryOversize
                }
 
                vs2 := *vs
@@ -314,13 +312,13 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
 
 func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
        if vs == nil {
-               return errors.Wrap(errMissingField, "empty value source")
+               return errors.Wrap(ErrMissingField, "empty value source")
        }
        if vs.Ref == nil {
-               return errors.Wrap(errMissingField, "missing ref on value source")
+               return errors.Wrap(ErrMissingField, "missing ref on value source")
        }
        if vs.Value == nil || vs.Value.AssetId == nil {
-               return errors.Wrap(errMissingField, "missing value on value source")
+               return errors.Wrap(ErrMissingField, "missing value on value source")
        }
 
        e, ok := vstate.tx.Entries[*vs.Ref]
@@ -338,25 +336,25 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
        switch ref := e.(type) {
        case *bc.Coinbase:
                if vs.Position != 0 {
-                       return errors.Wrapf(errPosition, "invalid position %d for coinbase source", vs.Position)
+                       return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
                }
                dest = ref.WitnessDestination
 
        case *bc.Issuance:
                if vs.Position != 0 {
-                       return errors.Wrapf(errPosition, "invalid position %d for issuance source", vs.Position)
+                       return errors.Wrapf(ErrPosition, "invalid position %d for issuance source", vs.Position)
                }
                dest = ref.WitnessDestination
 
        case *bc.Spend:
                if vs.Position != 0 {
-                       return errors.Wrapf(errPosition, "invalid position %d for spend source", vs.Position)
+                       return errors.Wrapf(ErrPosition, "invalid position %d for spend source", vs.Position)
                }
                dest = ref.WitnessDestination
 
        case *bc.Mux:
                if vs.Position >= uint64(len(ref.WitnessDestinations)) {
-                       return errors.Wrapf(errPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
+                       return errors.Wrapf(ErrPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
                }
                dest = ref.WitnessDestinations[vs.Position]
 
@@ -365,19 +363,19 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
        }
 
        if dest.Ref == nil || *dest.Ref != vstate.entryID {
-               return errors.Wrapf(errMismatchedReference, "value source for %x has disagreeing destination %x", vstate.entryID.Bytes(), dest.Ref.Bytes())
+               return errors.Wrapf(ErrMismatchedReference, "value source for %x has disagreeing destination %x", vstate.entryID.Bytes(), dest.Ref.Bytes())
        }
 
        if dest.Position != vstate.sourcePos {
-               return errors.Wrapf(errMismatchedPosition, "value source position %d disagrees with %d", dest.Position, vstate.sourcePos)
+               return errors.Wrapf(ErrMismatchedPosition, "value source position %d disagrees with %d", dest.Position, vstate.sourcePos)
        }
 
        eq, err := dest.Value.Equal(vs.Value)
        if err != nil {
-               return errors.Sub(errMissingField, err)
+               return errors.Sub(ErrMissingField, err)
        }
        if !eq {
-               return errors.Wrapf(errMismatchedValue, "source value %v disagrees with %v", dest.Value, vs.Value)
+               return errors.Wrapf(ErrMismatchedValue, "source value %v disagrees with %v", dest.Value, vs.Value)
        }
 
        return nil
@@ -385,13 +383,13 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
 
 func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
        if vd == nil {
-               return errors.Wrap(errMissingField, "empty value destination")
+               return errors.Wrap(ErrMissingField, "empty value destination")
        }
        if vd.Ref == nil {
-               return errors.Wrap(errMissingField, "missing ref on value destination")
+               return errors.Wrap(ErrMissingField, "missing ref on value destination")
        }
        if vd.Value == nil || vd.Value.AssetId == nil {
-               return errors.Wrap(errMissingField, "missing value on value source")
+               return errors.Wrap(ErrMissingField, "missing value on value source")
        }
 
        e, ok := vs.tx.Entries[*vd.Ref]
@@ -403,19 +401,19 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
        switch ref := e.(type) {
        case *bc.Output:
                if vd.Position != 0 {
-                       return errors.Wrapf(errPosition, "invalid position %d for output destination", vd.Position)
+                       return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
                }
                src = ref.Source
 
        case *bc.Retirement:
                if vd.Position != 0 {
-                       return errors.Wrapf(errPosition, "invalid position %d for retirement destination", vd.Position)
+                       return errors.Wrapf(ErrPosition, "invalid position %d for retirement destination", vd.Position)
                }
                src = ref.Source
 
        case *bc.Mux:
                if vd.Position >= uint64(len(ref.Sources)) {
-                       return errors.Wrapf(errPosition, "invalid position %d for %d-source mux destination", vd.Position, len(ref.Sources))
+                       return errors.Wrapf(ErrPosition, "invalid position %d for %d-source mux destination", vd.Position, len(ref.Sources))
                }
                src = ref.Sources[vd.Position]
 
@@ -424,19 +422,19 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
        }
 
        if src.Ref == nil || *src.Ref != vs.entryID {
-               return errors.Wrapf(errMismatchedReference, "value destination for %x has disagreeing source %x", vs.entryID.Bytes(), src.Ref.Bytes())
+               return errors.Wrapf(ErrMismatchedReference, "value destination for %x has disagreeing source %x", vs.entryID.Bytes(), src.Ref.Bytes())
        }
 
        if src.Position != vs.destPos {
-               return errors.Wrapf(errMismatchedPosition, "value destination position %d disagrees with %d", src.Position, vs.destPos)
+               return errors.Wrapf(ErrMismatchedPosition, "value destination position %d disagrees with %d", src.Position, vs.destPos)
        }
 
        eq, err := src.Value.Equal(vd.Value)
        if err != nil {
-               return errors.Sub(errMissingField, err)
+               return errors.Sub(ErrMissingField, err)
        }
        if !eq {
-               return errors.Wrapf(errMismatchedValue, "destination value %v disagrees with %v", src.Value, vd.Value)
+               return errors.Wrapf(ErrMismatchedValue, "destination value %v disagrees with %v", src.Value, vd.Value)
        }
 
        return nil
@@ -454,7 +452,7 @@ func checkStandardTx(tx *bc.Tx) error {
                }
 
                if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
-                       return errNotStandardTx
+                       return ErrNotStandardTx
                }
        }
 
@@ -470,7 +468,7 @@ func checkStandardTx(tx *bc.Tx) error {
                }
 
                if !segwit.IsP2WScript(output.ControlProgram.Code) {
-                       return errNotStandardTx
+                       return ErrNotStandardTx
                }
        }
        return nil
@@ -482,7 +480,7 @@ func checkTimeRange(tx *bc.Tx, block *bc.Block) error {
        }
 
        if tx.TimeRange < block.Height {
-               return errBadTimeRange
+               return ErrBadTimeRange
        }
        return nil
 }
@@ -491,10 +489,10 @@ func checkTimeRange(tx *bc.Tx, block *bc.Block) error {
 func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) {
        gasStatus := &GasState{GasValid: false}
        if block.Version == 1 && tx.Version != 1 {
-               return gasStatus, errors.WithDetailf(errTxVersion, "block version %d, transaction version %d", block.Version, tx.Version)
+               return gasStatus, errors.WithDetailf(ErrTxVersion, "block version %d, transaction version %d", block.Version, tx.Version)
        }
        if tx.SerializedSize == 0 {
-               return gasStatus, errWrongTransactionSize
+               return gasStatus, ErrWrongTransactionSize
        }
        if err := checkTimeRange(tx, block); err != nil {
                return gasStatus, err
index b9453b0..8dcce7e 100644 (file)
@@ -57,7 +57,7 @@ func TestGasStatus(t *testing.T) {
                        f: func(input *GasState) error {
                                return input.setGas(-10000, 0)
                        },
-                       err: errGasCalculate,
+                       err: ErrGasCalculate,
                },
                {
                        input: &GasState{
@@ -89,7 +89,7 @@ func TestGasStatus(t *testing.T) {
                        f: func(input *GasState) error {
                                return input.updateUsage(-1)
                        },
-                       err: errGasCalculate,
+                       err: ErrGasCalculate,
                },
                {
                        input: &GasState{
@@ -155,47 +155,47 @@ func TestOverflow(t *testing.T) {
                {
                        inputs:  []uint64{math.MaxUint64, 1},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{math.MaxUint64, math.MaxUint64},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{math.MaxUint64, math.MaxUint64 - 1},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{math.MaxInt64, 1},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{math.MaxInt64, math.MaxInt64},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{math.MaxInt64, math.MaxInt64 - 1},
                        outputs: []uint64{0},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{0},
                        outputs: []uint64{math.MaxUint64},
-                       err:     errOverflow,
+                       err:     ErrOverflow,
                },
                {
                        inputs:  []uint64{0},
                        outputs: []uint64{math.MaxInt64},
-                       err:     errGasCalculate,
+                       err:     ErrGasCalculate,
                },
                {
                        inputs:  []uint64{math.MaxInt64 - 1},
                        outputs: []uint64{math.MaxInt64},
-                       err:     errGasCalculate,
+                       err:     ErrGasCalculate,
                },
        }
 
@@ -265,14 +265,14 @@ func TestTxValidation(t *testing.T) {
                                iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
                                iss.WitnessDestination.Value.Amount++
                        },
-                       err: errUnbalanced,
+                       err: ErrUnbalanced,
                },
                {
                        desc: "unbalanced mux amounts",
                        f: func() {
                                mux.WitnessDestinations[0].Value.Amount++
                        },
-                       err: errUnbalanced,
+                       err: ErrUnbalanced,
                },
                {
                        desc: "balanced mux amounts",
@@ -289,7 +289,7 @@ func TestTxValidation(t *testing.T) {
                                iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
                                iss.WitnessDestination.Value.Amount = math.MaxInt64
                        },
-                       err: errOverflow,
+                       err: ErrOverflow,
                },
                {
                        desc: "underflowing mux destination amounts",
@@ -301,7 +301,7 @@ func TestTxValidation(t *testing.T) {
                                out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
                                out.Source.Value.Amount = math.MaxInt64
                        },
-                       err: errOverflow,
+                       err: ErrOverflow,
                },
                {
                        desc: "unbalanced mux assets",
@@ -310,14 +310,14 @@ func TestTxValidation(t *testing.T) {
                                sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
                                sp.WitnessDestination.Value.AssetId = newAssetID(255)
                        },
-                       err: errUnbalanced,
+                       err: ErrUnbalanced,
                },
                {
                        desc: "mismatched output source / mux dest position",
                        f: func() {
                                tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
                        },
-                       err: errMismatchedPosition,
+                       err: ErrMismatchedPosition,
                },
                {
                        desc: "mismatched output source and mux dest",
@@ -333,14 +333,14 @@ func TestTxValidation(t *testing.T) {
                                tx.Entries[*out2ID] = out2
                                mux.WitnessDestinations[0].Ref = out2ID
                        },
-                       err: errMismatchedReference,
+                       err: ErrMismatchedReference,
                },
                {
                        desc: "invalid mux destination position",
                        f: func() {
                                mux.WitnessDestinations[0].Position = 1
                        },
-                       err: errPosition,
+                       err: ErrPosition,
                },
                {
                        desc: "mismatched mux dest value / output source value",
@@ -353,14 +353,14 @@ func TestTxValidation(t *testing.T) {
                                }
                                mux.Sources[0].Value.Amount++ // the mux must still balance
                        },
-                       err: errMismatchedValue,
+                       err: ErrMismatchedValue,
                },
                {
                        desc: "empty tx results",
                        f: func() {
                                tx.ResultIds = nil
                        },
-                       err: errEmptyResults,
+                       err: ErrEmptyResults,
                },
                {
                        desc: "empty tx results, but that's OK",
@@ -395,14 +395,14 @@ func TestTxValidation(t *testing.T) {
                                        Amount:  spend.WitnessDestination.Value.Amount + 1,
                                }
                        },
-                       err: errMismatchedValue,
+                       err: ErrMismatchedValue,
                },
                {
                        desc: "gas out of limit",
                        f: func() {
                                vs.tx.SerializedSize = 10000000
                        },
-                       err: errOverGasCredit,
+                       err: ErrOverGasCredit,
                },
                {
                        desc: "can't find gas spend input in entries",
@@ -444,7 +444,7 @@ func TestTxValidation(t *testing.T) {
                                        Amount:  spend.WitnessDestination.Value.Amount + 1,
                                }
                        },
-                       err: errMismatchedValue,
+                       err: ErrMismatchedValue,
                },
                {
                        desc: "mismatched witness asset destination",
@@ -453,7 +453,7 @@ func TestTxValidation(t *testing.T) {
                                issuance := tx.Entries[*issuanceID].(*bc.Issuance)
                                issuance.WitnessAssetDefinition.Data = &bc.Hash{V0: 9999}
                        },
-                       err: errMismatchedAssetID,
+                       err: ErrMismatchedAssetID,
                },
                {
                        desc: "issuance witness position greater than length of mux sources",
@@ -462,7 +462,7 @@ func TestTxValidation(t *testing.T) {
                                issuance := tx.Entries[*issuanceID].(*bc.Issuance)
                                issuance.WitnessDestination.Position = uint64(len(mux.Sources) + 1)
                        },
-                       err: errPosition,
+                       err: ErrPosition,
                },
                {
                        desc: "normal coinbase tx",
@@ -476,7 +476,7 @@ func TestTxValidation(t *testing.T) {
                        f: func() {
                                addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
                        },
-                       err: errWrongCoinbaseAsset,
+                       err: ErrWrongCoinbaseAsset,
                },
                {
                        desc: "coinbase tx is not first tx in block",
@@ -484,7 +484,7 @@ func TestTxValidation(t *testing.T) {
                                addCoinbase(consensus.BTMAssetID, 100000, nil)
                                vs.block.Transactions[0] = nil
                        },
-                       err: errWrongCoinbaseTransaction,
+                       err: ErrWrongCoinbaseTransaction,
                },
                {
                        desc: "coinbase arbitrary size out of limit",
@@ -492,7 +492,7 @@ func TestTxValidation(t *testing.T) {
                                arbitrary := make([]byte, consensus.CoinbaseArbitrarySizeLimit+1)
                                addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
                        },
-                       err: errCoinbaseArbitraryOversize,
+                       err: ErrCoinbaseArbitraryOversize,
                },
                {
                        desc: "normal retirement output",
@@ -532,7 +532,7 @@ func TestTxValidation(t *testing.T) {
                                }
                                mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
                        },
-                       err: errNoSource,
+                       err: ErrNoSource,
                },
        }
 
@@ -787,14 +787,7 @@ func mockGasTxInput() *types.TxInput {
 
 // Like errors.Root, but also unwraps vm.Error objects.
 func rootErr(e error) error {
-       for {
-               e = errors.Root(e)
-               if e2, ok := e.(vm.Error); ok {
-                       e = e2.Err
-                       continue
-               }
-               return e
-       }
+       return errors.Root(e)
 }
 
 func hashData(data []byte) bc.Hash {
index 54def8e..4d08f9a 100644 (file)
@@ -17,7 +17,6 @@ var (
        ErrShortProgram       = errors.New("unexpected end of program")
        ErrToken              = errors.New("unrecognized token")
        ErrUnexpected         = errors.New("unexpected error")
-       ErrUnsupportedTx      = errors.New("unsupported transaction type")
        ErrUnsupportedVM      = errors.New("unsupported VM")
        ErrVerifyFailed       = errors.New("VERIFY failed")
 )
index 910672f..c9f8e13 100644 (file)
@@ -35,6 +35,7 @@ type virtualMachine struct {
 // execution.
 var TraceOut io.Writer
 
+// Verify program by running VM
 func Verify(context *Context, gasLimit int64) (gasLeft int64, err error) {
        defer func() {
                if r := recover(); r != nil {
@@ -212,33 +213,20 @@ func stackCost(stack [][]byte) int64 {
        return result
 }
 
-type Error struct {
-       Err  error
-       Prog []byte
-       Args [][]byte
-}
+func wrapErr(err error, vm *virtualMachine, args [][]byte) error {
+       if err == nil {
+               return nil
+       }
 
-func (e Error) Error() string {
-       dis, err := Disassemble(e.Prog)
-       if err != nil {
+       dis, errDis := Disassemble(vm.program)
+       if errDis != nil {
                dis = "???"
        }
 
-       args := make([]string, 0, len(e.Args))
-       for _, a := range e.Args {
-               args = append(args, hex.EncodeToString(a))
+       dataArgs := make([]string, 0, len(args))
+       for _, a := range args {
+               dataArgs = append(dataArgs, hex.EncodeToString(a))
        }
 
-       return fmt.Sprintf("%s [prog %x = %s; args %s]", e.Err.Error(), e.Prog, dis, strings.Join(args, " "))
-}
-
-func wrapErr(err error, vm *virtualMachine, args [][]byte) error {
-       if err == nil {
-               return nil
-       }
-       return Error{
-               Err:  err,
-               Prog: vm.program,
-               Args: args,
-       }
+       return errors.Wrap(err, fmt.Sprintf("%s [prog %x = %s; args %s]", err.Error(), vm.program, dis, strings.Join(dataArgs, " ")))
 }