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"
"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 {
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
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"},
"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
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 != "" {
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 != "" {
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
}
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)
}
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
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)
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]
}
if output.Source.Value.Amount != amount {
- return errors.Wrap(errWrongCoinbaseTransaction, "dismatch output amount")
+ return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output amount")
}
return nil
}
}),
},
amount: 6000,
- err: errWrongCoinbaseTransaction,
+ err: ErrWrongCoinbaseTransaction,
},
{
txs: []*types.Tx{},
amount: 5000,
- err: errWrongCoinbaseTransaction,
+ err: ErrWrongCoinbaseTransaction,
},
}
"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 {
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 {
}
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
}
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
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
}
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)
}
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
}
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
}
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)
}
}
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)
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 {
}
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(),
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
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]
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]
}
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
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]
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]
}
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
}
if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
- return errNotStandardTx
+ return ErrNotStandardTx
}
}
}
if !segwit.IsP2WScript(output.ControlProgram.Code) {
- return errNotStandardTx
+ return ErrNotStandardTx
}
}
return nil
}
if tx.TimeRange < block.Height {
- return errBadTimeRange
+ return ErrBadTimeRange
}
return nil
}
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
f: func(input *GasState) error {
return input.setGas(-10000, 0)
},
- err: errGasCalculate,
+ err: ErrGasCalculate,
},
{
input: &GasState{
f: func(input *GasState) error {
return input.updateUsage(-1)
},
- err: errGasCalculate,
+ err: ErrGasCalculate,
},
{
input: &GasState{
{
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,
},
}
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",
iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
iss.WitnessDestination.Value.Amount = math.MaxInt64
},
- err: errOverflow,
+ err: ErrOverflow,
},
{
desc: "underflowing mux destination amounts",
out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
out.Source.Value.Amount = math.MaxInt64
},
- err: errOverflow,
+ err: ErrOverflow,
},
{
desc: "unbalanced mux assets",
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",
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",
}
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",
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",
Amount: spend.WitnessDestination.Value.Amount + 1,
}
},
- err: errMismatchedValue,
+ err: ErrMismatchedValue,
},
{
desc: "mismatched witness asset destination",
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",
issuance := tx.Entries[*issuanceID].(*bc.Issuance)
issuance.WitnessDestination.Position = uint64(len(mux.Sources) + 1)
},
- err: errPosition,
+ err: ErrPosition,
},
{
desc: "normal coinbase tx",
f: func() {
addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
},
- err: errWrongCoinbaseAsset,
+ err: ErrWrongCoinbaseAsset,
},
{
desc: "coinbase tx is not first tx in block",
addCoinbase(consensus.BTMAssetID, 100000, nil)
vs.block.Transactions[0] = nil
},
- err: errWrongCoinbaseTransaction,
+ err: ErrWrongCoinbaseTransaction,
},
{
desc: "coinbase arbitrary size out of limit",
arbitrary := make([]byte, consensus.CoinbaseArbitrarySizeLimit+1)
addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
},
- err: errCoinbaseArbitraryOversize,
+ err: ErrCoinbaseArbitraryOversize,
},
{
desc: "normal retirement output",
}
mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
},
- err: errNoSource,
+ err: ErrNoSource,
},
}
// 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 {
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")
)
// 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 {
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, " ")))
}