Repository: ovrclk/akash
Pull request #750 (2020-07-17 19:51:07)

feat:740 Order Bidding TTL
Provide an automatic cancellation of Market Orders. A maximum Order
duration is specifed as a

Current Order bidding duration is 1 day. Assuming 7s/block written; this
totals to 12342 blocks for one day. If the chain height is beyond that,
then the order is signaled to be closed.

fixes: #740

codecov[bot]: # [Codecov](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=h1) Report
> Merging [#750](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=desc) into [master](https://codecov.io/gh/ovrclk/akash/commit/26e321d3d2e282a437b382d4b2fc725b8a3e5ef2&el=desc) will **increase** coverage by `0.22%`.
> The diff coverage is `76.19%`.

[![Impacted file tree graph](https://codecov.io/gh/ovrclk/akash/pull/750/graphs/tree.svg?width=650&height=150&src=pr&token=QXe3zmrbI2)](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #750      +/-   ##
==========================================
+ Coverage   32.80%   33.02%   +0.22%     
==========================================
  Files         152      152              
  Lines        7832     7849      +17     
==========================================
+ Hits         2569     2592      +23     
+ Misses       5142     5134       -8     
- Partials      121      123       +2     
```


| [Impacted Files](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [x/deployment/types/types.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-eC9kZXBsb3ltZW50L3R5cGVzL3R5cGVzLmdv) | `91.66% <ø> (ø)` | |
| [x/market/handler/endblock.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-eC9tYXJrZXQvaGFuZGxlci9lbmRibG9jay5nbw==) | `26.66% <0.00%> (-1.12%)` | :arrow_down: |
| [x/market/keeper/keeper.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-eC9tYXJrZXQva2VlcGVyL2tlZXBlci5nbw==) | `89.61% <60.00%> (-0.70%)` | :arrow_down: |
| [sdl/v1.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-c2RsL3YxLmdv) | `76.74% <100.00%> (+0.18%)` | :arrow_up: |
| [x/market/types/types.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-eC9tYXJrZXQvdHlwZXMvdHlwZXMuZ28=) | `87.75% <100.00%> (+37.75%)` | :arrow_up: |
| [validation/resources.go](https://codecov.io/gh/ovrclk/akash/pull/750/diff?src=pr&el=tree#diff-dmFsaWRhdGlvbi9yZXNvdXJjZXMuZ28=) | `46.66% <0.00%> (-5.00%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=footer). Last update [26e321d...98d7999](https://codecov.io/gh/ovrclk/akash/pull/750?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/sdl/v1.go b/sdl/v1.go
index 8cd1320c..65a65cf3 100644
--- a/sdl/v1.go
+++ b/sdl/v1.go
@@ -3,9 +3,9 @@ package sdl
 import (
 	"sort"
 
+	"github.com/blang/semver"
 	"github.com/pkg/errors"
 
-	"github.com/blang/semver"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/ovrclk/akash/manifest"
@@ -172,6 +172,8 @@ func (sdl *v1) DeploymentGroups() ([]*dtypes.GroupSpec, error) {
 				Count: svcdepl.Count,
 			})
 
+			// TODO: Make a parameter to configure the duration of orders being bid on
+			group.OrderBidDuration = dtypes.DefaultOrderBiddingDuration
 		}
 	}
 
diff --git a/validation/deployment.go b/validation/deployment.go
index d29300e8..b29d8a8b 100644
--- a/validation/deployment.go
+++ b/validation/deployment.go
@@ -19,7 +19,8 @@ func ValidateDeploymentGroups(gspecs []dtypes.GroupSpec) error {
 	}
 
 	for _, group := range gspecs {
-		if err := validateGroupPricing(defaultConfig, group); err != nil {
+		err := ValidateDeploymentGroup(group)
+		if err != nil {
 			return err
 		}
 	}
@@ -28,6 +29,10 @@ func ValidateDeploymentGroups(gspecs []dtypes.GroupSpec) error {
 
 // ValidateDeploymentGroup does validation for provided deployment group
 func ValidateDeploymentGroup(gspec dtypes.GroupSpec) error {
+	if err := gspec.ValidateBasic(); err != nil {
+		return errors.Wrapf(err, "group validation error: %v", gspec.Name)
+	}
+
 	if err := validateResourceList(defaultConfig, gspec); err != nil {
 		return err
 	}
diff --git a/x/deployment/types/errors.go b/x/deployment/types/errors.go
index 9eed6823..1bb0fee6 100644
--- a/x/deployment/types/errors.go
+++ b/x/deployment/types/errors.go
@@ -20,6 +20,7 @@ const (
 	errGroupNotFound
 	errGroupClosed
 	errGroupNotOpen
+	errGroupSpec
 )
 
 var (
@@ -53,4 +54,6 @@ var (
 	ErrGroupClosed = sdkerrors.Register(ModuleName, errGroupClosed, "Group already closed")
 	// ErrGroupNotOpen indicates the Group state has progressed beyond initial Open.
 	ErrGroupNotOpen = sdkerrors.Register(ModuleName, errGroupNotOpen, "Group not open")
+	// ErrGroupSpecInvalid indicates a GroupSpec has invalid configuration
+	ErrGroupSpecInvalid = sdkerrors.Register(ModuleName, errGroupSpec, "GroupSpec invalid")
 )
diff --git a/x/deployment/types/msgs.go b/x/deployment/types/msgs.go
index f8a8ff23..f1b9d042 100644
--- a/x/deployment/types/msgs.go
+++ b/x/deployment/types/msgs.go
@@ -45,6 +45,12 @@ func (msg MsgCreateDeployment) ValidateBasic() error {
 	if len(msg.Version) == 0 {
 		return ErrEmptyVersion
 	}
+	for _, gs := range msg.Groups {
+		err := gs.ValidateBasic()
+		if err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
diff --git a/x/deployment/types/types.go b/x/deployment/types/types.go
index e90ff058..90b68728 100644
--- a/x/deployment/types/types.go
+++ b/x/deployment/types/types.go
@@ -6,6 +6,14 @@ import (
 	"github.com/ovrclk/akash/types"
 )
 
+// DefaultOrderBiddingDuration is the default time limit for an Order being active.
+// After the duration, the Order is automatically closed.
+// ( 24(hr) * 3600(seconds per hour) ) / 7s-Block
+const DefaultOrderBiddingDuration int64 = int64(12342)
+
+// MaxBiddingDuration is roughly 30 days of block height
+const MaxBiddingDuration int64 = DefaultOrderBiddingDuration * int64(30)
+
 //go:generate stringer -linecomment -output=autogen_stringer.go -type=DeploymentState,GroupState
 
 // DeploymentState defines state of deployment
@@ -54,9 +62,24 @@ const (
 
 // GroupSpec stores group specifications
 type GroupSpec struct {
-	Name         string          `json:"name"`
-	Requirements []sdk.Attribute `json:"requirements"`
-	Resources    []Resource      `json:"resources"`
+	Name             string          `json:"name"`
+	Requirements     []sdk.Attribute `json:"requirements"`
+	Resources        []Resource      `json:"resources"`
+	OrderBidDuration int64           `json:"bid-duration"`
+}
+
+// ValidateBasic asserts non-zero values
+func (g GroupSpec) ValidateBasic() error {
+	if g.OrderBidDuration < 1 {
+		return ErrInvalidGroups
+	}
+	if g.OrderBidDuration > MaxBiddingDuration {
+		return ErrInvalidGroups
+	}
+	if g.Name == "" {
+		return ErrInvalidGroups
+	}
+	return nil
 }
 
 // GetResources method returns resources list in group
diff --git a/x/market/handler/endblock.go b/x/market/handler/endblock.go
index 1b7b656f..60c2f694 100644
--- a/x/market/handler/endblock.go
+++ b/x/market/handler/endblock.go
@@ -103,6 +103,10 @@ func pickBidWinner(bids []types.Bid) (winner *types.Bid, err error) {
 func matchOrders(ctx sdk.Context, keepers Keepers) error {
 	keepers.Market.WithOpenOrders(ctx, func(order types.Order) bool {
 		if err := order.ValidateCanMatch(ctx.BlockHeight()); err != nil {
+			if errors.Is(err, types.ErrOrderDurationExceeded) {
+				keepers.Market.OnOrderClosed(ctx, order) // change order state to closed
+				return false
+			}
 			return false
 		}
 
diff --git a/x/market/keeper/keeper.go b/x/market/keeper/keeper.go
index 881b293f..fce47d04 100644
--- a/x/market/keeper/keeper.go
+++ b/x/market/keeper/keeper.go
@@ -52,11 +52,15 @@ func (k Keeper) CreateOrder(ctx sdk.Context, gid dtypes.GroupID, spec dtypes.Gro
 		OrderID: types.MakeOrderID(gid, oseq),
 		Spec:    spec,
 		State:   types.OrderOpen,
-		StartAt: ctx.BlockHeight() + orderTTL, // TODO: check overflow
+		StartAt: ctx.BlockHeight() + orderTTL,                         // TODO: check overflow
+		CloseAt: ctx.BlockHeight() + orderTTL + spec.OrderBidDuration, // TODO: check overflow, set via parameter
 	}
 
 	key := orderKey(order.ID())
-	// XXX TODO: check not overwrite
+	if store.Has(key) { // check no overwrite
+		return types.Order{}, types.ErrOrderExists
+	}
+
 	store.Set(key, k.cdc.MustMarshalBinaryBare(order))
 	k.updateOpenOrderIndex(store, order)
 
diff --git a/x/market/types/errors.go b/x/market/types/errors.go
index fbda640c..c24894b9 100644
--- a/x/market/types/errors.go
+++ b/x/market/types/errors.go
@@ -25,6 +25,9 @@ const (
 	errCodeInvalidPrice
 	errCodeOrderMatched
 	errCodeOrderClosed
+	errCodeOrderExists
+	errCodeOrderDurationExceeded
+	errCodeOrderTooEarly
 )
 
 var (
@@ -68,4 +71,10 @@ var (
 	ErrOrderMatched = sdkerrors.New(ModuleName, errCodeOrderMatched, "order matched")
 	// ErrOrderClosed order closed
 	ErrOrderClosed = sdkerrors.New(ModuleName, errCodeOrderClosed, "order closed")
+	// ErrOrderExists indicates a new order was proposed overwrite the existing store key
+	ErrOrderExists = sdkerrors.New(ModuleName, errCodeOrderExists, "order already exists in store")
+	// ErrOrderTooEarly to match bid
+	ErrOrderTooEarly = sdkerrors.New(ModuleName, errCodeOrderTooEarly, "order: chain height to low for bidding")
+	// ErrOrderDurationExceeded order should be closed
+	ErrOrderDurationExceeded = sdkerrors.New(ModuleName, errCodeOrderDurationExceeded, "order duration has exceeded the bidding duration")
 )
diff --git a/x/market/types/types.go b/x/market/types/types.go
index 6b834390..4fa329ed 100644
--- a/x/market/types/types.go
+++ b/x/market/types/types.go
@@ -1,9 +1,12 @@
 package types
 
 import (
+	"fmt"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/pkg/errors"
 
+	"github.com/ovrclk/akash/validation"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
 )
 
@@ -35,6 +38,7 @@ type Order struct {
 
 	// block height to start matching
 	StartAt int64            `json:"start-at"`
+	CloseAt int64            `json:"close-at"`
 	Spec    dtypes.GroupSpec `json:"spec"`
 }
 
@@ -84,9 +88,16 @@ func (o Order) ValidateCanMatch(height int64) error {
 	if err := o.validateMatchableState(); err != nil {
 		return err
 	}
+	if err := validation.ValidateDeploymentGroup(o.Spec); err != nil {
+		return err
+	}
 
-	if o.StartAt > height {
-		return errors.Errorf("too early to match order (%v > %v)", o.StartAt, height)
+	if height < o.StartAt {
+		return errors.Wrap(ErrOrderTooEarly, fmt.Sprintf("(%v > %v)", o.StartAt, height))
+	}
+	if height >= o.CloseAt {
+		// Close Open Order if it have surpassed the CloseAt block height.
+		return ErrOrderDurationExceeded
 	}
 	return nil
 }


Test patch:
diff --git a/integration/node_test.go b/integration/node_test.go
index 88cbc6d7..f8b63789 100644
--- a/integration/node_test.go
+++ b/integration/node_test.go
@@ -50,7 +50,6 @@ func TestAddGenesisAccount(t *testing.T) {
 
 	cdc := app.MakeCodec()
 	accounts := auth.GetGenesisStateFromAppState(cdc, genesisState)
-	// t.Logf("Accounts:%v\n", accounts)
 	require.Equal(t, accounts.Accounts[0].GetAddress(), f.KeyAddress(keyFoo))
 	require.Equal(t, accounts.Accounts[1].GetAddress(), f.KeyAddress(keyBar))
 	require.True(t, accounts.Accounts[0].GetCoins().IsEqual(startCoins()))
diff --git a/testutil/deployment.go b/testutil/deployment.go
index 9b3f8031..26aa4791 100644
--- a/testutil/deployment.go
+++ b/testutil/deployment.go
@@ -32,17 +32,29 @@ func DeploymentGroup(t testing.TB, did dtypes.DeploymentID, gseq uint32) dtypes.
 		GroupID: dtypes.MakeGroupID(did, gseq),
 		State:   dtypes.GroupOpen,
 		GroupSpec: dtypes.GroupSpec{
-			Name:         Name(t, "dgroup"),
-			Requirements: Attributes(t),
-			Resources:    Resources(t),
+			Name:             Name(t, "dgroup"),
+			Requirements:     Attributes(t),
+			Resources:        Resources(t),
+			OrderBidDuration: dtypes.DefaultOrderBiddingDuration,
 		},
 	}
 }
 
+// GroupSpec generator
+func GroupSpec(t testing.TB) dtypes.GroupSpec {
+	t.Helper()
+	return dtypes.GroupSpec{
+		Name:             Name(t, "dgroup"),
+		Requirements:     Attributes(t),
+		Resources:        Resources(t),
+		OrderBidDuration: dtypes.DefaultOrderBiddingDuration,
+	}
+}
+
 // DeploymentGroups returns a set of deployment groups generated by DeploymentGroup
 func DeploymentGroups(t testing.TB, did dtypes.DeploymentID, gseq uint32) []dtypes.Group {
 	t.Helper()
-	count := rand.Intn(10) + 5
+	count := rand.Intn(5) + 5
 	vals := make([]dtypes.Group, 0, count)
 	for i := 0; i < count; i++ {
 		vals = append(vals, DeploymentGroup(t, did, gseq+uint32(i)))
diff --git a/validation/deployment_test.go b/validation/deployment_test.go
new file mode 100644
index 00000000..9214ec1a
--- /dev/null
+++ b/validation/deployment_test.go
@@ -0,0 +1,51 @@
+package validation_test
+
+import (
+	"testing"
+
+	"github.com/ovrclk/akash/testutil"
+	"github.com/ovrclk/akash/validation"
+	dtypes "github.com/ovrclk/akash/x/deployment/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestZeroValueGroupSpec(t *testing.T) {
+	did := testutil.DeploymentID(t)
+	dgroup := testutil.DeploymentGroup(t, did, uint32(6))
+	gspec := dgroup.GroupSpec
+
+	t.Run("assert nominal test success", func(t *testing.T) {
+		err := validation.ValidateDeploymentGroup(gspec)
+		require.NoError(t, err)
+	})
+
+	gspec.OrderBidDuration = int64(0)
+	t.Run("assert error for zero value bid duration", func(t *testing.T) {
+		err := validation.ValidateDeploymentGroup(gspec)
+		require.Error(t, err)
+	})
+}
+
+func TestZeroValueGroupSpecs(t *testing.T) {
+	did := testutil.DeploymentID(t)
+	dgroups := testutil.DeploymentGroups(t, did, uint32(6))
+	gspecs := make([]dtypes.GroupSpec, 0)
+	for _, d := range dgroups {
+		gspecs = append(gspecs, d.GroupSpec)
+	}
+
+	t.Run("assert nominal test success", func(t *testing.T) {
+		err := validation.ValidateDeploymentGroups(gspecs)
+		require.NoError(t, err)
+	})
+
+	gspecZeroed := make([]dtypes.GroupSpec, len(gspecs))
+	for _, g := range gspecs {
+		g.OrderBidDuration = int64(0)
+		gspecZeroed = append(gspecZeroed, g)
+	}
+	t.Run("assert error for zero value bid duration", func(t *testing.T) {
+		err := validation.ValidateDeploymentGroups(gspecZeroed)
+		require.Error(t, err)
+	})
+}
diff --git a/x/deployment/handler/endblock_test.go b/x/deployment/handler/endblock_test.go
index a6cd21b6..dd11c6ce 100644
--- a/x/deployment/handler/endblock_test.go
+++ b/x/deployment/handler/endblock_test.go
@@ -55,7 +55,6 @@ func TestEndBlock(t *testing.T) {
 	for _, g := range gx {
 		orderCreated := false
 		suite.mkeeper.WithOrdersForGroup(suite.ctx, g.ID(), func(o mtypes.Order) bool {
-			suite.t.Logf("Order for group: %#v found", o.GroupID())
 			orderCreated = true
 			return true
 		})
diff --git a/x/deployment/types/events_test.go b/x/deployment/types/events_test.go
index 8e947308..146c7359 100644
--- a/x/deployment/types/events_test.go
+++ b/x/deployment/types/events_test.go
@@ -28,7 +28,6 @@ type testEventParsing struct {
 func (tep testEventParsing) testMessageType() func(t *testing.T) {
 	_, err := ParseEvent(tep.msg)
 	return func(t *testing.T) {
-		t.Logf("%+v", tep)
 		// if the error expected is errWildcard to catch untyped errors, don't fail the test, the error was expected.
 		if errors.Is(tep.expErr, errWildcard) {
 			require.Error(t, err)
diff --git a/x/deployment/types/types_test.go b/x/deployment/types/types_test.go
index 3c4295d7..42217ca7 100644
--- a/x/deployment/types/types_test.go
+++ b/x/deployment/types/types_test.go
@@ -48,8 +48,7 @@ func TestGroupState(t *testing.T) {
 		},
 	}
 
-	for i, test := range tests {
-		t.Logf("------test-%d: %#v", i, test)
+	for _, test := range tests {
 		group := types.Group{
 			GroupID: testutil.GroupID(t),
 			State:   test.state,
@@ -105,3 +104,57 @@ func TestDeploymentVersionAttributeLifecycle(t *testing.T) {
 		assert.NotEqual(t, d.Version, versionString)
 	})
 }
+
+func TestGroupSpecValidation(t *testing.T) {
+	tests := []struct {
+		desc   string
+		gspec  types.GroupSpec
+		expErr error
+	}{
+		{
+			desc: "zero value bid duration error",
+			gspec: types.GroupSpec{
+				Name:             testutil.Name(t, "groupspec"),
+				Requirements:     testutil.Attributes(t),
+				Resources:        testutil.Resources(t),
+				OrderBidDuration: int64(0),
+			},
+			expErr: types.ErrInvalidGroups,
+		},
+		{
+			desc: "bid duration exceeds limit",
+			gspec: types.GroupSpec{
+				Name:             testutil.Name(t, "groupspec"),
+				Requirements:     testutil.Attributes(t),
+				Resources:        testutil.Resources(t),
+				OrderBidDuration: types.MaxBiddingDuration * int64(2),
+			},
+			expErr: types.ErrInvalidGroups,
+		},
+		{
+			desc: "groupspec requires name",
+			gspec: types.GroupSpec{
+				Name:             "",
+				Requirements:     testutil.Attributes(t),
+				Resources:        testutil.Resources(t),
+				OrderBidDuration: types.DefaultOrderBiddingDuration,
+			},
+			expErr: types.ErrInvalidGroups,
+		},
+		{
+			desc: "groupspec valid",
+			gspec: types.GroupSpec{
+				Name:             "hihi",
+				Requirements:     testutil.Attributes(t),
+				Resources:        testutil.Resources(t),
+				OrderBidDuration: types.DefaultOrderBiddingDuration,
+			},
+			expErr: nil,
+		},
+	}
+
+	for _, test := range tests {
+		err := test.gspec.ValidateBasic()
+		assert.Equal(t, test.expErr, err, test.desc)
+	}
+}
diff --git a/x/market/handler/endblock_external_test.go b/x/market/handler/endblock_external_test.go
index c350bc2e..483def3e 100644
--- a/x/market/handler/endblock_external_test.go
+++ b/x/market/handler/endblock_external_test.go
@@ -98,6 +98,14 @@ func TestMatchOrders(t *testing.T) {
 		})
 		assert.Equal(t, len(openGroups), count)
 	})
+	t.Run("assert leases *not* created", func(t *testing.T) {
+		count := 0
+		suite.MarketKeeper().WithActiveLeases(suite.Context(), func(l types.Lease) bool {
+			count++
+			return false
+		})
+		assert.Equal(t, count, 0)
+	})
 
 	t.Run("match orders after setting block height", func(t *testing.T) {
 		suite.SetBlockHeight(int64(100))
@@ -119,6 +127,14 @@ func TestMatchOrders(t *testing.T) {
 		})
 		assert.Equal(t, count, 0)
 	})
+	t.Run("assert any leases created???", func(t *testing.T) {
+		count := 0
+		suite.MarketKeeper().WithLeases(suite.Context(), func(l types.Lease) bool {
+			count++
+			return false
+		})
+		assert.Equal(t, len(openGroups), count)
+	})
 	t.Run("assert leases created", func(t *testing.T) {
 		count := 0
 		suite.MarketKeeper().WithActiveLeases(suite.Context(), func(l types.Lease) bool {
diff --git a/x/market/handler/endblock_test.go b/x/market/handler/endblock_test.go
index 9a52e96d..172709f9 100644
--- a/x/market/handler/endblock_test.go
+++ b/x/market/handler/endblock_test.go
@@ -25,7 +25,6 @@ func (w *winnerTest) testFunc(t *testing.T) {
 	}
 	if w.expWinner != nil && !winner.Equals(w.expWinner.ID()) {
 		t.Errorf("unexpected winner: %#v\n%q : %v", winner, types.BidIDString(winner.BidID), winner.Price)
-		t.Logf("winner: %+v coin: %s", winner.ID(), winner.Price.String())
 	}
 }
 
@@ -133,9 +132,6 @@ func (td *testDist) testFunc(t *testing.T) {
 	r := float64(td.rounds/td.bidNum) * 0.5
 
 	for i := 0; i < td.bidNum; i++ {
-
-		t.Logf("[%d] winner distribution %v (%v expected)", i, distributionSpread[i], r)
-
 		v := float64(distributionSpread[i])
 
 		if v >= r {
diff --git a/x/market/types/events_test.go b/x/market/types/events_test.go
index 7b0cc643..5c8f186e 100644
--- a/x/market/types/events_test.go
+++ b/x/market/types/events_test.go
@@ -30,7 +30,6 @@ type testEventParsing struct {
 func (tep testEventParsing) testMessageType() func(t *testing.T) {
 	_, err := ParseEvent(tep.msg)
 	return func(t *testing.T) {
-		t.Logf("%+v", tep)
 		// if the error expected is errWildcard to catch untyped errors, don't fail the test, the error was expected.
 		if errors.Is(tep.expErr, errWildcard) {
 			require.Error(t, err)
diff --git a/x/market/types/types_test.go b/x/market/types/types_test.go
new file mode 100644
index 00000000..9231af47
--- /dev/null
+++ b/x/market/types/types_test.go
@@ -0,0 +1,115 @@
+package types_test
+
+import (
+	"testing"
+
+	"github.com/ovrclk/akash/testutil"
+	"github.com/ovrclk/akash/x/market/types"
+	"github.com/pkg/errors"
+
+	dtypes "github.com/ovrclk/akash/x/deployment/types"
+)
+
+func TestOrderValidateCanMatch(t *testing.T) {
+	baseHeight := int64(10000)
+	boostHeight := int64(50)
+
+	type testCase struct {
+		desc   string
+		order  types.Order
+		height int64
+		expErr error
+	}
+	tests := []testCase{
+		{
+			desc: "zero values",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				StartAt: baseHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: baseHeight,
+			expErr: types.ErrOrderClosed,
+		},
+		{
+			desc: "zero value CloseAt",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderOpen,
+				StartAt: baseHeight,
+				// CloseAt is 0
+				Spec: testutil.GroupSpec(t),
+			},
+			height: baseHeight + dtypes.DefaultOrderBiddingDuration + boostHeight,
+			expErr: types.ErrOrderDurationExceeded,
+		},
+		{
+			desc: "open order returns no error",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderOpen,
+				StartAt: baseHeight,
+				CloseAt: baseHeight + boostHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: baseHeight + boostHeight - int64(10),
+			expErr: nil,
+		},
+		{
+			desc: "block height beyond CloseAt",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderOpen,
+				StartAt: baseHeight,
+				CloseAt: baseHeight + boostHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: baseHeight + boostHeight + boostHeight,
+			expErr: types.ErrOrderDurationExceeded,
+		},
+		{
+			desc: "state errors makes sense",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderClosed,
+				StartAt: baseHeight,
+				CloseAt: baseHeight + boostHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: int64(120),
+			expErr: types.ErrOrderClosed,
+		},
+		{
+			desc: "assert order matched error",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderMatched,
+				StartAt: baseHeight,
+				CloseAt: baseHeight + boostHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: int64(80),
+			expErr: types.ErrOrderMatched,
+		},
+		{
+			desc: "height less than allowed start",
+			order: types.Order{
+				OrderID: testutil.OrderID(t),
+				State:   types.OrderOpen,
+				StartAt: baseHeight,
+				CloseAt: baseHeight + boostHeight,
+				Spec:    testutil.GroupSpec(t),
+			},
+			height: int64(90),
+			expErr: types.ErrOrderTooEarly,
+		},
+	}
+	for _, test := range tests {
+		err := test.order.ValidateCanMatch(test.height)
+		if !errors.Is(err, test.expErr) { // cannot use assert.Equal due to lack of support for errorstack
+			t.Error(test.desc, ": ", err)
+		} else if test.expErr != nil && err == nil {
+			t.Error("expected error but none returned")
+		}
+	}
+}
diff --git a/x/provider/types/events_test.go b/x/provider/types/events_test.go
index df8cd90b..d77fddde 100644
--- a/x/provider/types/events_test.go
+++ b/x/provider/types/events_test.go
@@ -26,7 +26,6 @@ type testEventParsing struct {
 func (tep testEventParsing) testMessageType() func(t *testing.T) {
 	_, err := ParseEvent(tep.msg)
 	return func(t *testing.T) {
-		t.Logf("%+v", tep)
 		// if the error expected is errWildcard to catch untyped errors, don't fail the test, the error was expected.
 		if errors.Is(tep.expErr, errWildcard) {
 			require.Error(t, err)
diff --git a/x/provider/types/msgs_test.go b/x/provider/types/msgs_test.go
index 2eeee78b..8e633cc3 100644
--- a/x/provider/types/msgs_test.go
+++ b/x/provider/types/msgs_test.go
@@ -98,11 +98,10 @@ func TestConfigPath(t *testing.T) {
 					err != nil && test.expErr == nil {
 					t.Errorf("unexpected error occurred: %v", err)
 
-					p, err := url.Parse(test.path)
+					_, err := url.Parse(test.path)
 					if err != nil {
 						t.Errorf("url.Parse() of %q err: %v", test.path, err)
 					}
-					t.Logf("%#v", p)
 				}
 			}
 			return testFunc