Repository: lightningnetwork/lnd
Pull request #3701 (2019-11-10 11:07:10)

tlv+hop: contain odd/even logic in payload parsing
Tlv is used more widely in lnd than just for the onion payload. This commit isolates the protocol-specific odd/even logic, so that tlv can be used freely elsewhere. An example of this use is db serialization.


Patch:
diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go
index 22adcbdc1..989786f1a 100644
--- a/contractcourt/briefcase.go
+++ b/contractcourt/briefcase.go
@@ -440,7 +440,7 @@ func (b *boltArbitratorLog) CommitState(s ArbitratorState) error {
 //
 // NOTE: Part of the ContractResolver interface.
 func (b *boltArbitratorLog) FetchUnresolvedContracts() ([]ContractResolver, error) {
-	resolverCfg := ResolverConfig{
+	resKit := ResolverKit{
 		ChannelArbitratorConfig: b.cfg,
 		Checkpoint:              b.checkpointContract,
 	}
@@ -469,38 +469,56 @@ func (b *boltArbitratorLog) FetchUnresolvedContracts() ([]ContractResolver, erro
 
 			switch resType {
 			case resolverTimeout:
-				res, err = newTimeoutResolverFromReader(
-					resReader, resolverCfg,
-				)
+				timeoutRes := &htlcTimeoutResolver{}
+				if err := timeoutRes.Decode(resReader); err != nil {
+					return err
+				}
+				timeoutRes.AttachResolverKit(resKit)
+
+				res = timeoutRes
 
 			case resolverSuccess:
-				res, err = newSuccessResolverFromReader(
-					resReader, resolverCfg,
-				)
+				successRes := &htlcSuccessResolver{}
+				if err := successRes.Decode(resReader); err != nil {
+					return err
+				}
+
+				res = successRes
 
 			case resolverOutgoingContest:
-				res, err = newOutgoingContestResolverFromReader(
-					resReader, resolverCfg,
-				)
+				outContestRes := &htlcOutgoingContestResolver{
+					htlcTimeoutResolver: htlcTimeoutResolver{},
+				}
+				if err := outContestRes.Decode(resReader); err != nil {
+					return err
+				}
+
+				res = outContestRes
 
 			case resolverIncomingContest:
-				res, err = newIncomingContestResolverFromReader(
-					resReader, resolverCfg,
-				)
+				inContestRes := &htlcIncomingContestResolver{
+					htlcSuccessResolver: htlcSuccessResolver{},
+				}
+				if err := inContestRes.Decode(resReader); err != nil {
+					return err
+				}
+
+				res = inContestRes
 
 			case resolverUnilateralSweep:
-				res, err = newCommitSweepResolverFromReader(
-					resReader, resolverCfg,
-				)
+				sweepRes := &commitSweepResolver{}
+				if err := sweepRes.Decode(resReader); err != nil {
+					return err
+				}
+
+				res = sweepRes
 
 			default:
 				return fmt.Errorf("unknown resolver type: %v", resType)
 			}
 
-			if err != nil {
-				return err
-			}
-
+			resKit.Quit = make(chan struct{})
+			res.AttachResolverKit(resKit)
 			contracts = append(contracts, res)
 			return nil
 		})
diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go
index 66730ed11..3bd669027 100644
--- a/contractcourt/channel_arbitrator.go
+++ b/contractcourt/channel_arbitrator.go
@@ -1685,7 +1685,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
 
 	// We'll create the resolver kit that we'll be cloning for each
 	// resolver so they each can do their duty.
-	resolverCfg := ResolverConfig{
+	resKit := ResolverKit{
 		ChannelArbitratorConfig: c.cfg,
 		Checkpoint: func(res ContractResolver) error {
 			return c.log.InsertUnresolvedContracts(res)
@@ -1733,10 +1733,14 @@ func (c *ChannelArbitrator) prepContractResolutions(
 					continue
 				}
 
-				resolver := newSuccessResolver(
-					resolution, height,
-					htlc.RHash, htlc.Amt, resolverCfg,
-				)
+				resKit.Quit = make(chan struct{})
+				resolver := &htlcSuccessResolver{
+					htlcResolution:  resolution,
+					broadcastHeight: height,
+					payHash:         htlc.RHash,
+					htlcAmt:         htlc.Amt,
+					ResolverKit:     resKit,
+				}
 				htlcResolvers = append(htlcResolvers, resolver)
 			}
 
@@ -1757,10 +1761,14 @@ func (c *ChannelArbitrator) prepContractResolutions(
 					continue
 				}
 
-				resolver := newTimeoutResolver(
-					resolution, height, htlc.HtlcIndex,
-					htlc.Amt, resolverCfg,
-				)
+				resKit.Quit = make(chan struct{})
+				resolver := &htlcTimeoutResolver{
+					htlcResolution:  resolution,
+					broadcastHeight: height,
+					htlcIndex:       htlc.HtlcIndex,
+					htlcAmt:         htlc.Amt,
+					ResolverKit:     resKit,
+				}
 				htlcResolvers = append(htlcResolvers, resolver)
 			}
 
@@ -1790,11 +1798,18 @@ func (c *ChannelArbitrator) prepContractResolutions(
 					ChanID: c.cfg.ShortChanID,
 				}
 
-				resolver := newIncomingContestResolver(
-					htlc.RefundTimeout, circuitKey,
-					resolution, height, htlc.RHash,
-					htlc.Amt, resolverCfg,
-				)
+				resKit.Quit = make(chan struct{})
+				resolver := &htlcIncomingContestResolver{
+					htlcExpiry: htlc.RefundTimeout,
+					circuitKey: circuitKey,
+					htlcSuccessResolver: htlcSuccessResolver{
+						htlcResolution:  resolution,
+						broadcastHeight: height,
+						payHash:         htlc.RHash,
+						htlcAmt:         htlc.Amt,
+						ResolverKit:     resKit,
+					},
+				}
 				htlcResolvers = append(htlcResolvers, resolver)
 			}
 
@@ -1816,10 +1831,16 @@ func (c *ChannelArbitrator) prepContractResolutions(
 					continue
 				}
 
-				resolver := newOutgoingContestResolver(
-					resolution, height, htlc.HtlcIndex,
-					htlc.Amt, resolverCfg,
-				)
+				resKit.Quit = make(chan struct{})
+				resolver := &htlcOutgoingContestResolver{
+					htlcTimeoutResolver: htlcTimeoutResolver{
+						htlcResolution:  resolution,
+						broadcastHeight: height,
+						htlcIndex:       htlc.HtlcIndex,
+						htlcAmt:         htlc.Amt,
+						ResolverKit:     resKit,
+					},
+				}
 				htlcResolvers = append(htlcResolvers, resolver)
 			}
 		}
@@ -1829,10 +1850,14 @@ func (c *ChannelArbitrator) prepContractResolutions(
 	// a resolver to sweep our commitment output (but only if it wasn't
 	// trimmed).
 	if contractResolutions.CommitResolution != nil {
-		resolver := newCommitSweepResolver(
-			*contractResolutions.CommitResolution,
-			height, c.cfg.ChanPoint, resolverCfg,
-		)
+		resKit.Quit = make(chan struct{})
+		resolver := &commitSweepResolver{
+			commitResolution: *contractResolutions.CommitResolution,
+			broadcastHeight:  height,
+			chanPoint:        c.cfg.ChanPoint,
+			ResolverKit:      resKit,
+		}
+
 		htlcResolvers = append(htlcResolvers, resolver)
 	}
 
diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go
index 3b7f3b855..4e5712728 100644
--- a/contractcourt/commit_sweep_resolver.go
+++ b/contractcourt/commit_sweep_resolver.go
@@ -36,20 +36,7 @@ type commitSweepResolver struct {
 	// chanPoint is the channel point of the original contract.
 	chanPoint wire.OutPoint
 
-	contractResolverKit
-}
-
-// newCommitSweepResolver instantiates a new direct commit output resolver.
-func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
-	broadcastHeight uint32,
-	chanPoint wire.OutPoint, resCfg ResolverConfig) *commitSweepResolver {
-
-	return &commitSweepResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-		commitResolution:    res,
-		broadcastHeight:     broadcastHeight,
-		chanPoint:           chanPoint,
-	}
+	ResolverKit
 }
 
 // ResolverKey returns an identifier which should be globally unique for this
@@ -93,7 +80,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
 			return nil, errResolverShuttingDown
 		}
 
-	case <-c.quit:
+	case <-c.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -151,7 +138,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
 
 			log.Infof("ChannelPoint(%v) commit tx is fully resolved by "+
 				"sweep tx: %v", c.chanPoint, sweepResult.Tx.TxHash())
-		case <-c.quit:
+		case <-c.Quit:
 			return nil, errResolverShuttingDown
 		}
 
@@ -193,7 +180,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
 			log.Errorf("unable to Checkpoint: %v", err)
 			return nil, err
 		}
-	case <-c.quit:
+	case <-c.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -219,7 +206,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
 		log.Infof("ChannelPoint(%v) commit tx is fully resolved, at height: %v",
 			c.chanPoint, confInfo.BlockHeight)
 
-	case <-c.quit:
+	case <-c.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -234,7 +221,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
 //
 // NOTE: Part of the ContractResolver interface.
 func (c *commitSweepResolver) Stop() {
-	close(c.quit)
+	close(c.Quit)
 }
 
 // IsResolved returns true if the stored state in the resolve is fully
@@ -275,40 +262,44 @@ func (c *commitSweepResolver) Encode(w io.Writer) error {
 	return nil
 }
 
-// newCommitSweepResolverFromReader attempts to decode an encoded
-// ContractResolver from the passed Reader instance, returning an active
-// ContractResolver instance.
-func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
-	*commitSweepResolver, error) {
-
-	c := &commitSweepResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-	}
-
+// Decode attempts to decode an encoded ContractResolver from the passed Reader
+// instance, returning an active ContractResolver instance.
+//
+// NOTE: Part of the ContractResolver interface.
+func (c *commitSweepResolver) Decode(r io.Reader) error {
 	if err := decodeCommitResolution(r, &c.commitResolution); err != nil {
-		return nil, err
+		return err
 	}
 
 	if err := binary.Read(r, endian, &c.resolved); err != nil {
-		return nil, err
+		return err
 	}
 	if err := binary.Read(r, endian, &c.broadcastHeight); err != nil {
-		return nil, err
+		return err
 	}
 	_, err := io.ReadFull(r, c.chanPoint.Hash[:])
 	if err != nil {
-		return nil, err
+		return err
 	}
 	err = binary.Read(r, endian, &c.chanPoint.Index)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	// Previously a sweep tx was deserialized at this point. Refactoring
 	// removed this, but keep in mind that this data may still be present in
 	// the database.
 
-	return c, nil
+	return nil
+}
+
+// AttachResolverKit should be called once a resolved is successfully decoded
+// from its stored format. This struct delivers a generic tool kit that
+// resolvers need to complete their duty.
+//
+// NOTE: Part of the ContractResolver interface.
+func (c *commitSweepResolver) AttachResolverKit(r ResolverKit) {
+	c.ResolverKit = r
 }
 
 // A compile time assertion to ensure commitSweepResolver meets the
diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go
index 7248bba05..b39a04d4a 100644
--- a/contractcourt/contract_resolvers.go
+++ b/contractcourt/contract_resolvers.go
@@ -46,6 +46,16 @@ type ContractResolver interface {
 	// passed Writer.
 	Encode(w io.Writer) error
 
+	// Decode attempts to decode an encoded ContractResolver from the
+	// passed Reader instance, returning an active ContractResolver
+	// instance.
+	Decode(r io.Reader) error
+
+	// AttachResolverKit should be called once a resolved is successfully
+	// decoded from its stored format. This struct delivers a generic tool
+	// kit that resolvers need to complete their duty.
+	AttachResolverKit(ResolverKit)
+
 	// Stop signals the resolver to cancel any current resolution
 	// processes, and suspend.
 	Stop()
@@ -59,9 +69,10 @@ type reportingContractResolver interface {
 	report() *ContractReport
 }
 
-// ResolverConfig contains the externally supplied configuration items that are
-// required by a ContractResolver implementation.
-type ResolverConfig struct {
+// ResolverKit is meant to be used as a mix-in struct to be embedded within a
+// given ContractResolver implementation. It contains all the items that a
+// resolver requires to carry out its duties.
+type ResolverKit struct {
 	// ChannelArbitratorConfig contains all the interfaces and closures
 	// required for the resolver to interact with outside sub-systems.
 	ChannelArbitratorConfig
@@ -70,23 +81,8 @@ type ResolverConfig struct {
 	// should write the state of the resolver to persistent storage, and
 	// return a non-nil error upon success.
 	Checkpoint func(ContractResolver) error
-}
-
-// contractResolverKit is meant to be used as a mix-in struct to be embedded within a
-// given ContractResolver implementation. It contains all the common items that
-// a resolver requires to carry out its duties.
-type contractResolverKit struct {
-	ResolverConfig
-
-	quit chan struct{}
-}
 
-// newContractResolverKit instantiates the mix-in struct.
-func newContractResolverKit(cfg ResolverConfig) *contractResolverKit {
-	return &contractResolverKit{
-		ResolverConfig: cfg,
-		quit:           make(chan struct{}),
-	}
+	Quit chan struct{}
 }
 
 var (
diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go
index 73bf5c5bc..d1c8716cb 100644
--- a/contractcourt/htlc_success_resolver.go
+++ b/contractcourt/htlc_success_resolver.go
@@ -52,22 +52,7 @@ type htlcSuccessResolver struct {
 	// account any fees that may have to be paid if it goes on chain.
 	htlcAmt lnwire.MilliSatoshi
 
-	contractResolverKit
-}
-
-// newSuccessResolver instanties a new htlc success resolver.
-func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
-	broadcastHeight uint32, payHash lntypes.Hash,
-	htlcAmt lnwire.MilliSatoshi,
-	resCfg ResolverConfig) *htlcSuccessResolver {
-
-	return &htlcSuccessResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-		htlcResolution:      res,
-		broadcastHeight:     broadcastHeight,
-		payHash:             payHash,
-		htlcAmt:             htlcAmt,
-	}
+	ResolverKit
 }
 
 // ResolverKey returns an identifier which should be globally unique for this
@@ -188,7 +173,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
 				return nil, errResolverShuttingDown
 			}
 
-		case <-h.quit:
+		case <-h.Quit:
 			return nil, errResolverShuttingDown
 		}
 
@@ -253,7 +238,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
 			return nil, errResolverShuttingDown
 		}
 
-	case <-h.quit:
+	case <-h.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -266,7 +251,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
 //
 // NOTE: Part of the ContractResolver interface.
 func (h *htlcSuccessResolver) Stop() {
-	close(h.quit)
+	close(h.Quit)
 }
 
 // IsResolved returns true if the stored state in the resolve is fully
@@ -305,37 +290,41 @@ func (h *htlcSuccessResolver) Encode(w io.Writer) error {
 	return nil
 }
 
-// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
-// from the passed Reader instance, returning an active ContractResolver
-// instance.
-func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
-	*htlcSuccessResolver, error) {
-
-	h := &htlcSuccessResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-	}
-
+// Decode attempts to decode an encoded ContractResolver from the passed Reader
+// instance, returning an active ContractResolver instance.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcSuccessResolver) Decode(r io.Reader) error {
 	// First we'll decode our inner HTLC resolution.
 	if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
-		return nil, err
+		return err
 	}
 
 	// Next, we'll read all the fields that are specified to the contract
 	// resolver.
 	if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
-		return nil, err
+		return err
 	}
 	if err := binary.Read(r, endian, &h.resolved); err != nil {
-		return nil, err
+		return err
 	}
 	if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
-		return nil, err
+		return err
 	}
 	if _, err := io.ReadFull(r, h.payHash[:]); err != nil {
-		return nil, err
+		return err
 	}
 
-	return h, nil
+	return nil
+}
+
+// AttachResolverKit should be called once a resolved is successfully decoded
+// from its stored format. This struct delivers a generic tool kit that
+// resolvers need to complete their duty.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcSuccessResolver) AttachResolverKit(r ResolverKit) {
+	h.ResolverKit = r
 }
 
 // A compile time assertion to ensure htlcSuccessResolver meets the
diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go
index 248cfa36b..9d1c92d72 100644
--- a/contractcourt/htlc_timeout_resolver.go
+++ b/contractcourt/htlc_timeout_resolver.go
@@ -48,22 +48,7 @@ type htlcTimeoutResolver struct {
 	// account any fees that may have to be paid if it goes on chain.
 	htlcAmt lnwire.MilliSatoshi
 
-	contractResolverKit
-}
-
-// newTimeoutResolver instantiates a new timeout htlc resolver.
-func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution,
-	broadcastHeight uint32, htlcIndex uint64,
-	htlcAmt lnwire.MilliSatoshi,
-	resCfg ResolverConfig) *htlcTimeoutResolver {
-
-	return &htlcTimeoutResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-		htlcResolution:      res,
-		broadcastHeight:     broadcastHeight,
-		htlcIndex:           htlcIndex,
-		htlcAmt:             htlcAmt,
-	}
+	ResolverKit
 }
 
 // ResolverKey returns an identifier which should be globally unique for this
@@ -289,7 +274,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
 				return errResolverShuttingDown
 			}
 
-		case <-h.quit:
+		case <-h.Quit:
 			return errResolverShuttingDown
 		}
 
@@ -327,7 +312,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
 			return nil, errResolverShuttingDown
 		}
 
-	case <-h.quit:
+	case <-h.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -380,7 +365,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
 //
 // NOTE: Part of the ContractResolver interface.
 func (h *htlcTimeoutResolver) Stop() {
-	close(h.quit)
+	close(h.Quit)
 }
 
 // IsResolved returns true if the stored state in the resolve is fully
@@ -421,39 +406,43 @@ func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
 	return nil
 }
 
-// newTimeoutResolverFromReader attempts to decode an encoded ContractResolver
-// from the passed Reader instance, returning an active ContractResolver
-// instance.
-func newTimeoutResolverFromReader(r io.Reader, resCfg ResolverConfig) (
-	*htlcTimeoutResolver, error) {
-
-	h := &htlcTimeoutResolver{
-		contractResolverKit: *newContractResolverKit(resCfg),
-	}
-
+// Decode attempts to decode an encoded ContractResolver from the passed Reader
+// instance, returning an active ContractResolver instance.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcTimeoutResolver) Decode(r io.Reader) error {
 	// First, we'll read out all the mandatory fields of the
 	// OutgoingHtlcResolution that we store.
 	if err := decodeOutgoingResolution(r, &h.htlcResolution); err != nil {
-		return nil, err
+		return err
 	}
 
 	// With those fields read, we can now read back the fields that are
 	// specific to the resolver itself.
 	if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
-		return nil, err
+		return err
 	}
 	if err := binary.Read(r, endian, &h.resolved); err != nil {
-		return nil, err
+		return err
 	}
 	if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
-		return nil, err
+		return err
 	}
 
 	if err := binary.Read(r, endian, &h.htlcIndex); err != nil {
-		return nil, err
+		return err
 	}
 
-	return h, nil
+	return nil
+}
+
+// AttachResolverKit should be called once a resolved is successfully decoded
+// from its stored format. This struct delivers a generic tool kit that
+// resolvers need to complete their duty.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcTimeoutResolver) AttachResolverKit(r ResolverKit) {
+	h.ResolverKit = r
 }
 
 // A compile time assertion to ensure htlcTimeoutResolver meets the
diff --git a/docker/README.md b/docker/README.md
index 1dc79dbbb..474188c50 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -63,12 +63,8 @@ bitcoin into.
 # Init bitcoin network env variable:
 $ export NETWORK="simnet" 
 
-# Create persistent volumes for alice and bob.
-docker volume create simnet_lnd_alice
-docker volume create simnet_lnd_bob
-
 # Run the "Alice" container and log into it:
-$ docker-compose run -d --name alice --volume simnet_lnd_alice:/root/.lnd lnd_btc
+$ docker-compose run -d --name alice lnd_btc
 $ docker exec -i -t alice bash
 
 # Generate a new backward compatible nested p2sh address for Alice:
@@ -94,7 +90,7 @@ Connect `Bob` node to `Alice` node.
 
 ```bash
 # Run "Bob" node and log into it:
-$ docker-compose run -d --name bob --volume simnet_lnd_bob:/root/.lnd lnd_btc
+$ docker-compose run -d --name bob lnd_btc
 $ docker exec -i -t bob bash
 
 # Get the identity pubkey of "Bob" node:
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 8794f688d..fc0971b19 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -9,8 +9,8 @@ services:
       build:
         context: btcd/
       volumes:
-        - shared:/rpc
-        - bitcoin:/data
+            - shared:/rpc
+            - bitcoin:/data
       environment:
         - RPCUSER
         - RPCPASS
@@ -41,8 +41,8 @@ services:
       build:
         context: ltcd/
       volumes:
-        - shared:/rpc
-        - litecoin:/data
+            - shared:/rpc
+            - litecoin:/data
       environment:
         - RPCUSER
         - RPCPASS
@@ -76,8 +76,7 @@ services:
           - CHAIN
           - DEBUG
         volumes:
-          - shared:/rpc
-          - lnd:/root/.lnd
+            - shared:/rpc
         entrypoint: ["./start-lnd.sh"]
 
     lnd_ltc:
@@ -103,11 +102,6 @@ volumes:
   bitcoin:
     driver: local
 
-  # lnd volume is used for persisting lnd application data and chain state
-  # during container lifecycle.
-  lnd:
-    driver: local
-
   # litecoin volume is needed for maintaining blockchain persistence
   # during ltcd container recreation.
   litecoin:
diff --git a/docker/lnd/start-lnd.sh b/docker/lnd/start-lnd.sh
index 1fb8502ff..d65c145dc 100755
--- a/docker/lnd/start-lnd.sh
+++ b/docker/lnd/start-lnd.sh
@@ -51,6 +51,7 @@ fi
 
 exec lnd \
     --noseedbackup \
+    --logdir="/data" \
     "--$CHAIN.active" \
     "--$CHAIN.$NETWORK" \
     "--$CHAIN.node"="btcd" \
diff --git a/go.mod b/go.mod
index 1014fef38..7dd469714 100644
--- a/go.mod
+++ b/go.mod
@@ -33,7 +33,7 @@ require (
 	github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect
 	github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect
 	github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
-	github.com/lightninglabs/neutrino v0.10.1-0.20191111074637-d9b036bf675c
+	github.com/lightninglabs/neutrino v0.10.0
 	github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a
 	github.com/lightningnetwork/lnd/queue v1.0.1
 	github.com/lightningnetwork/lnd/ticker v1.0.0
diff --git a/go.sum b/go.sum
index 9003927df..98e1313a5 100644
--- a/go.sum
+++ b/go.sum
@@ -134,8 +134,6 @@ github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d h1:tt8hwvxl6fk
 github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
 github.com/lightninglabs/neutrino v0.10.0 h1:yWVy2cOCCXbKFdpYCE9vD1fWRJDd9FtGXhUws4l9RkU=
 github.com/lightninglabs/neutrino v0.10.0/go.mod h1:C3KhCMk1Mcx3j8v0qRVWM1Ow6rIJSvSPnUAq00ZNAfk=
-github.com/lightninglabs/neutrino v0.10.1-0.20191111074637-d9b036bf675c h1:FDuM1xfX51eK7LLeW6WCndeViiqLEKLnRL4P2ak1J/Q=
-github.com/lightninglabs/neutrino v0.10.1-0.20191111074637-d9b036bf675c/go.mod h1:C3KhCMk1Mcx3j8v0qRVWM1Ow6rIJSvSPnUAq00ZNAfk=
 github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a h1:GoWPN4i4jTKRxhVNh9a2vvBBO1Y2seiJB+SopUYoKyo=
 github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go
index e05b4ee98..3d15f4e03 100644
--- a/htlcswitch/hop/payload.go
+++ b/htlcswitch/hop/payload.go
@@ -29,6 +29,12 @@ const (
 	RequiredViolation
 )
 
+const (
+	// customTypeStart is the start of the custom tlv type range as defined
+	// in BOLT 01.
+	customTypeStart = 65536
+)
+
 // String returns a human-readable description of the violation as a verb.
 func (v PayloadViolation) String() string {
 	switch v {
@@ -124,28 +130,6 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
 
 	parsedTypes, err := tlvStream.DecodeWithParsedTypes(r)
 	if err != nil {
-		// Promote any required type failures into ErrInvalidPayload.
-		if e, required := err.(tlv.ErrUnknownRequiredType); required {
-			// If the parser returned an unknown required type
-			// failure, we'll first check that the payload is
-			// properly formed according to our known set of
-			// constraints. If an error is discovered, this
-			// overrides the required type failure.
-			nextHop := lnwire.NewShortChanIDFromInt(cid)
-			err = ValidateParsedPayloadTypes(parsedTypes, nextHop)
-			if err != nil {
-				return nil, err
-			}
-
-			// Otherwise the known constraints were applied
-			// successfully, report the invalid type failure
-			// returned by the parser.
-			return nil, ErrInvalidPayload{
-				Type:      tlv.Type(e),
-				Violation: RequiredViolation,
-				FinalHop:  nextHop == Exit,
-			}
-		}
 		return nil, err
 	}
 
@@ -157,6 +141,16 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
 		return nil, err
 	}
 
+	// Check for violation of the rules for mandatory fields.
+	violatingType := getMinRequiredViolation(parsedTypes)
+	if violatingType != nil {
+		return nil, ErrInvalidPayload{
+			Type:      *violatingType,
+			Violation: RequiredViolation,
+			FinalHop:  nextHop == Exit,
+		}
+	}
+
 	// If no MPP field was parsed, set the MPP field on the resulting
 	// payload to nil.
 	if _, ok := parsedTypes[record.MPPOnionType]; !ok {
@@ -239,3 +233,35 @@ func ValidateParsedPayloadTypes(parsedTypes tlv.TypeSet,
 func (h *Payload) MultiPath() *record.MPP {
 	return h.MPP
 }
+
+// getMinRequiredViolation checks for unrecognized required (even) fields in the
+// standard range and returns the lowest required type. Always returning the
+// lowest required type allows a failure message to be deterministic.
+func getMinRequiredViolation(set tlv.TypeSet) *tlv.Type {
+	var (
+		requiredViolation        bool
+		minRequiredViolationType tlv.Type
+	)
+	for t, known := range set {
+		// If a type is even but not known to us, we cannot process the
+		// payload. We are required to understand a field that we don't
+		// support.
+		//
+		// We always accept custom fields, because a higher level
+		// application may understand them.
+		if known || t%2 != 0 || t >= customTypeStart {
+			continue
+		}
+
+		if !requiredViolation || t < minRequiredViolationType {
+			minRequiredViolationType = t
+		}
+		requiredViolation = true
+	}
+
+	if requiredViolation {
+		return &minRequiredViolationType
+	}
+
+	return nil
+}
diff --git a/macaroons/store.go b/macaroons/store.go
index 6b80ef5a9..7a38dd92e 100644
--- a/macaroons/store.go
+++ b/macaroons/store.go
@@ -5,7 +5,6 @@ import (
 	"crypto/rand"
 	"fmt"
 	"io"
-	"sync"
 
 	"github.com/coreos/bbolt"
 
@@ -48,8 +47,7 @@ var (
 type RootKeyStorage struct {
 	*bbolt.DB
 
-	encKeyMtx sync.RWMutex
-	encKey    *snacl.SecretKey
+	encKey *snacl.SecretKey
 }
 
 // NewRootKeyStorage creates a RootKeyStorage instance.
@@ -65,15 +63,12 @@ func NewRootKeyStorage(db *bbolt.DB) (*RootKeyStorage, error) {
 	}
 
 	// Return the DB wrapped in a RootKeyStorage object.
-	return &RootKeyStorage{DB: db, encKey: nil}, nil
+	return &RootKeyStorage{db, nil}, nil
 }
 
 // CreateUnlock sets an encryption key if one is not already set, otherwise it
 // checks if the password is correct for the stored encryption key.
 func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
-	r.encKeyMtx.Lock()
-	defer r.encKeyMtx.Unlock()
-
 	// Check if we've already unlocked the store; return an error if so.
 	if r.encKey != nil {
 		return ErrAlreadyUnlocked
@@ -124,9 +119,6 @@ func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
 
 // Get implements the Get method for the bakery.RootKeyStorage interface.
 func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
-	r.encKeyMtx.RLock()
-	defer r.encKeyMtx.RUnlock()
-
 	if r.encKey == nil {
 		return nil, ErrStoreLocked
 	}
@@ -158,9 +150,6 @@ func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
 // interface.
 // TODO(aakselrod): Add support for key rotation.
 func (r *RootKeyStorage) RootKey(_ context.Context) ([]byte, []byte, error) {
-	r.encKeyMtx.RLock()
-	defer r.encKeyMtx.RUnlock()
-
 	if r.encKey == nil {
 		return nil, nil, ErrStoreLocked
 	}
@@ -206,9 +195,6 @@ func (r *RootKeyStorage) RootKey(_ context.Context) ([]byte, []byte, error) {
 // Close closes the underlying database and zeroes the encryption key stored
 // in memory.
 func (r *RootKeyStorage) Close() error {
-	r.encKeyMtx.Lock()
-	defer r.encKeyMtx.Unlock()
-
 	if r.encKey != nil {
 		r.encKey.Zero()
 	}
diff --git a/tlv/record.go b/tlv/record.go
index fe7742635..b9e7980d2 100644
--- a/tlv/record.go
+++ b/tlv/record.go
@@ -12,8 +12,9 @@ import (
 // Type is an 64-bit identifier for a TLV Record.
 type Type uint64
 
-// TypeSet is an unordered set of Types.
-type TypeSet map[Type]struct{}
+// TypeSet is an unordered set of Types. The map item boolean values indicate
+// whether the type that we parsed was known.
+type TypeSet map[Type]bool
 
 // Encoder is a signature for methods that can encode TLV values. An error
 // should be returned if the Encoder cannot support the underlying type of val.
diff --git a/tlv/stream.go b/tlv/stream.go
index d104a2067..ed2c0d000 100644
--- a/tlv/stream.go
+++ b/tlv/stream.go
@@ -2,7 +2,6 @@ package tlv
 
 import (
 	"errors"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"math"
@@ -22,15 +21,6 @@ var ErrStreamNotCanonical = errors.New("tlv stream is not canonical")
 // long to parse.
 var ErrRecordTooLarge = errors.New("record is too large")
 
-// ErrUnknownRequiredType is an error returned when decoding an unknown and even
-// type from a Stream.
-type ErrUnknownRequiredType Type
-
-// Error returns a human-readable description of unknown required type.
-func (t ErrUnknownRequiredType) Error() string {
-	return fmt.Sprintf("unknown required type: %d", t)
-}
-
 // Stream defines a TLV stream that can be used for encoding or decoding a set
 // of TLV Records.
 type Stream struct {
@@ -162,7 +152,6 @@ func (s *Stream) decode(r io.Reader, parsedTypes TypeSet) (TypeSet, error) {
 	var (
 		typ       Type
 		min       Type
-		firstFail *Type
 		recordIdx int
 		overflow  bool
 	)
@@ -177,10 +166,7 @@ func (s *Stream) decode(r io.Reader, parsedTypes TypeSet) (TypeSet, error) {
 		// We'll silence an EOF when zero bytes remain, meaning the
 		// stream was cleanly encoded.
 		case err == io.EOF:
-			if firstFail == nil {
-				return parsedTypes, nil
-			}
-			return parsedTypes, ErrUnknownRequiredType(*firstFail)
+			return parsedTypes, nil
 
 		// Other unexpected errors.
 		case err != nil:
@@ -244,31 +230,6 @@ func (s *Stream) decode(r io.Reader, parsedTypes TypeSet) (TypeSet, error) {
 				return nil, err
 			}
 
-		// This record type is unknown to the stream, fail if the type
-		// is even meaning that we are required to understand it.
-		case typ%2 == 0:
-			// We'll fail immediately in the case that we aren't
-			// tracking the set of parsed types.
-			if parsedTypes == nil {
-				return nil, ErrUnknownRequiredType(typ)
-			}
-
-			// Otherwise, we'll track the first such failure and
-			// allow parsing to continue. If no other types of
-			// errors are encountered, the first failure will be
-			// returned as an ErrUnknownRequiredType so that the
-			// full set of included types can be returned.
-			if firstFail == nil {
-				failTyp := typ
-				firstFail = &failTyp
-			}
-
-			// With the failure type recorded, we'll simply discard
-			// the remainder of the record as if it were optional.
-			// The first failure will be returned after reaching the
-			// stopping condition.
-			fallthrough
-
 		// Otherwise, the record type is unknown and is odd, discard the
 		// number of bytes specified by length.
 		default:
@@ -289,7 +250,7 @@ func (s *Stream) decode(r io.Reader, parsedTypes TypeSet) (TypeSet, error) {
 		// Record the successfully decoded or ignored type if the
 		// caller provided an initialized TypeSet.
 		if parsedTypes != nil {
-			parsedTypes[typ] = struct{}{}
+			parsedTypes[typ] = ok
 		}
 
 		// Update our record index so that we can begin our next search


Test patch:
diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go
index 5a40369cb..2f3348e6e 100644
--- a/contractcourt/htlc_incoming_contest_resolver.go
+++ b/contractcourt/htlc_incoming_contest_resolver.go
@@ -5,12 +5,11 @@ import (
 	"errors"
 	"io"
 
-	"github.com/btcsuite/btcutil"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/invoices"
+
+	"github.com/btcsuite/btcutil"
 	"github.com/lightningnetwork/lnd/lntypes"
-	"github.com/lightningnetwork/lnd/lnwallet"
-	"github.com/lightningnetwork/lnd/lnwire"
 )
 
 // htlcIncomingContestResolver is a ContractResolver that's able to resolve an
@@ -35,24 +34,6 @@ type htlcIncomingContestResolver struct {
 	htlcSuccessResolver
 }
 
-// newIncomingContestResolver instantiates a new incoming htlc contest resolver.
-func newIncomingContestResolver(htlcExpiry uint32,
-	circuitKey channeldb.CircuitKey, res lnwallet.IncomingHtlcResolution,
-	broadcastHeight uint32, payHash lntypes.Hash,
-	htlcAmt lnwire.MilliSatoshi,
-	resCfg ResolverConfig) *htlcIncomingContestResolver {
-
-	success := newSuccessResolver(
-		res, broadcastHeight, payHash, htlcAmt, resCfg,
-	)
-
-	return &htlcIncomingContestResolver{
-		htlcExpiry:          htlcExpiry,
-		circuitKey:          circuitKey,
-		htlcSuccessResolver: *success,
-	}
-}
-
 // Resolve attempts to resolve this contract. As we don't yet know of the
 // preimage for the contract, we'll wait for one of two things to happen:
 //
@@ -87,7 +68,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
 			return nil, errResolverShuttingDown
 		}
 		currentHeight = newBlock.Height
-	case <-h.quit:
+	case <-h.Quit:
 		return nil, errResolverShuttingDown
 	}
 
@@ -258,7 +239,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
 				return nil, h.Checkpoint(h)
 			}
 
-		case <-h.quit:
+		case <-h.Quit:
 			return nil, errResolverShuttingDown
 		}
 	}
@@ -290,7 +271,7 @@ func (h *htlcIncomingContestResolver) report() *ContractReport {
 //
 // NOTE: Part of the ContractResolver interface.
 func (h *htlcIncomingContestResolver) Stop() {
-	close(h.quit)
+	close(h.Quit)
 }
 
 // IsResolved returns true if the stored state in the resolve is fully
@@ -315,27 +296,27 @@ func (h *htlcIncomingContestResolver) Encode(w io.Writer) error {
 	return h.htlcSuccessResolver.Encode(w)
 }
 
-// newIncomingContestResolverFromReader attempts to decode an encoded ContractResolver
-// from the passed Reader instance, returning an active ContractResolver
-// instance.
-func newIncomingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
-	*htlcIncomingContestResolver, error) {
-
-	h := &htlcIncomingContestResolver{}
-
+// Decode attempts to decode an encoded ContractResolver from the passed Reader
+// instance, returning an active ContractResolver instance.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcIncomingContestResolver) Decode(r io.Reader) error {
 	// We'll first read the one field unique to this resolver.
 	if err := binary.Read(r, endian, &h.htlcExpiry); err != nil {
-		return nil, err
+		return err
 	}
 
 	// Then we'll decode our internal resolver.
-	successResolver, err := newSuccessResolverFromReader(r, resCfg)
-	if err != nil {
-		return nil, err
-	}
-	h.htlcSuccessResolver = *successResolver
+	return h.htlcSuccessResolver.Decode(r)
+}
 
-	return h, nil
+// AttachResolverKit should be called once a resolved is successfully decoded
+// from its stored format. This struct delivers a generic tool kit that
+// resolvers need to complete their duty.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcIncomingContestResolver) AttachResolverKit(r ResolverKit) {
+	h.ResolverKit = r
 }
 
 // A compile time assertion to ensure htlcIncomingContestResolver meets the
diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go
index 1f28f0a50..43d01817b 100644
--- a/contractcourt/htlc_incoming_resolver_test.go
+++ b/contractcourt/htlc_incoming_resolver_test.go
@@ -199,18 +199,17 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
 		},
 	}
 
-	cfg := ResolverConfig{
-		ChannelArbitratorConfig: chainCfg,
-		Checkpoint: func(_ ContractResolver) error {
-			checkPointChan <- struct{}{}
-			return nil
-		},
-	}
 	resolver := &htlcIncomingContestResolver{
 		htlcSuccessResolver: htlcSuccessResolver{
-			contractResolverKit: *newContractResolverKit(cfg),
-			htlcResolution:      lnwallet.IncomingHtlcResolution{},
-			payHash:             testResHash,
+			ResolverKit: ResolverKit{
+				ChannelArbitratorConfig: chainCfg,
+				Checkpoint: func(_ ContractResolver) error {
+					checkPointChan <- struct{}{}
+					return nil
+				},
+			},
+			htlcResolution: lnwallet.IncomingHtlcResolution{},
+			payHash:        testResHash,
 		},
 		htlcExpiry: testHtlcExpiry,
 	}
diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go
index 7388b45a8..789558a67 100644
--- a/contractcourt/htlc_outgoing_contest_resolver.go
+++ b/contractcourt/htlc_outgoing_contest_resolver.go
@@ -5,8 +5,6 @@ import (
 	"io"
 
 	"github.com/btcsuite/btcutil"
-	"github.com/lightningnetwork/lnd/lnwallet"
-	"github.com/lightningnetwork/lnd/lnwire"
 )
 
 // htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
@@ -20,21 +18,6 @@ type htlcOutgoingContestResolver struct {
 	htlcTimeoutResolver
 }
 
-// newOutgoingContestResolver instantiates a new outgoing contested htlc
-// resolver.
-func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
-	broadcastHeight uint32, htlcIndex uint64, htlcAmt lnwire.MilliSatoshi,
-	resCfg ResolverConfig) *htlcOutgoingContestResolver {
-
-	timeout := newTimeoutResolver(
-		res, broadcastHeight, htlcIndex, htlcAmt, resCfg,
-	)
-
-	return &htlcOutgoingContestResolver{
-		htlcTimeoutResolver: *timeout,
-	}
-}
-
 // Resolve commences the resolution of this contract. As this contract hasn't
 // yet timed out, we'll wait for one of two things to happen
 //
@@ -147,7 +130,7 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
 			// claimed.
 			return h.claimCleanUp(commitSpend)
 
-		case <-h.quit:
+		case <-h.Quit:
 			return nil, fmt.Errorf("resolver canceled")
 		}
 	}
@@ -179,7 +162,7 @@ func (h *htlcOutgoingContestResolver) report() *ContractReport {
 //
 // NOTE: Part of the ContractResolver interface.
 func (h *htlcOutgoingContestResolver) Stop() {
-	close(h.quit)
+	close(h.Quit)
 }
 
 // IsResolved returns true if the stored state in the resolve is fully
@@ -198,19 +181,21 @@ func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
 	return h.htlcTimeoutResolver.Encode(w)
 }
 
-// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
-// from the passed Reader instance, returning an active ContractResolver
-// instance.
-func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
-	*htlcOutgoingContestResolver, error) {
+// Decode attempts to decode an encoded ContractResolver from the passed Reader
+// instance, returning an active ContractResolver instance.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcOutgoingContestResolver) Decode(r io.Reader) error {
+	return h.htlcTimeoutResolver.Decode(r)
+}
 
-	h := &htlcOutgoingContestResolver{}
-	timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
-	if err != nil {
-		return nil, err
-	}
-	h.htlcTimeoutResolver = *timeoutResolver
-	return h, nil
+// AttachResolverKit should be called once a resolved is successfully decoded
+// from its stored format. This struct delivers a generic tool kit that
+// resolvers need to complete their duty.
+//
+// NOTE: Part of the ContractResolver interface.
+func (h *htlcOutgoingContestResolver) AttachResolverKit(r ResolverKit) {
+	h.ResolverKit = r
 }
 
 // A compile time assertion to ensure htlcOutgoingContestResolver meets the
diff --git a/contractcourt/htlc_outgoing_contest_resolver_test.go b/contractcourt/htlc_outgoing_contest_resolver_test.go
index 1f7dbed13..244b438d3 100644
--- a/contractcourt/htlc_outgoing_contest_resolver_test.go
+++ b/contractcourt/htlc_outgoing_contest_resolver_test.go
@@ -122,18 +122,16 @@ func newOutgoingResolverTestContext(t *testing.T) *outgoingResolverTestContext {
 		},
 	}
 
-	cfg := ResolverConfig{
-		ChannelArbitratorConfig: chainCfg,
-		Checkpoint: func(_ ContractResolver) error {
-			checkPointChan <- struct{}{}
-			return nil
-		},
-	}
-
 	resolver := &htlcOutgoingContestResolver{
 		htlcTimeoutResolver: htlcTimeoutResolver{
-			contractResolverKit: *newContractResolverKit(cfg),
-			htlcResolution:      outgoingRes,
+			ResolverKit: ResolverKit{
+				ChannelArbitratorConfig: chainCfg,
+				Checkpoint: func(_ ContractResolver) error {
+					checkPointChan <- struct{}{}
+					return nil
+				},
+			},
+			htlcResolution: outgoingRes,
 		},
 	}
 
diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go
index 981cefc41..7dcf025ad 100644
--- a/contractcourt/htlc_timeout_resolver_test.go
+++ b/contractcourt/htlc_timeout_resolver_test.go
@@ -237,17 +237,14 @@ func TestHtlcTimeoutResolver(t *testing.T) {
 			},
 		}
 
-		cfg := ResolverConfig{
-			ChannelArbitratorConfig: chainCfg,
-			Checkpoint: func(_ ContractResolver) error {
-				checkPointChan <- struct{}{}
-				return nil
-			},
-		}
 		resolver := &htlcTimeoutResolver{
-			contractResolverKit: *newContractResolverKit(
-				cfg,
-			),
+			ResolverKit: ResolverKit{
+				ChannelArbitratorConfig: chainCfg,
+				Checkpoint: func(_ ContractResolver) error {
+					checkPointChan <- struct{}{}
+					return nil
+				},
+			},
 		}
 		resolver.htlcResolution.SweepSignDesc = *fakeSignDesc
 
diff --git a/htlcswitch/hop/payload_test.go b/htlcswitch/hop/payload_test.go
index 7ef35e8b4..4092ff488 100644
--- a/htlcswitch/hop/payload_test.go
+++ b/htlcswitch/hop/payload_test.go
@@ -130,6 +130,12 @@ var decodePayloadTests = []decodePayloadTest{
 			FinalHop:  false,
 		},
 	},
+	{
+		name: "required type in custom range",
+		payload: []byte{0x02, 0x00, 0x04, 0x00,
+			0xfe, 0x00, 0x01, 0x00, 0x00, 0x00,
+		},
+	},
 	{
 		name: "valid intermediate hop",
 		payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00,
diff --git a/tlv/stream_test.go b/tlv/stream_test.go
index f7d54f730..8e4a33b77 100644
--- a/tlv/stream_test.go
+++ b/tlv/stream_test.go
@@ -9,49 +9,38 @@ import (
 )
 
 type parsedTypeTest struct {
-	name   string
-	encode []tlv.Type
-	decode []tlv.Type
-	expErr error
+	name           string
+	encode         []tlv.Type
+	decode         []tlv.Type
+	expParsedTypes tlv.TypeSet
 }
 
 // TestParsedTypes asserts that a Stream will properly return the set of types
 // that it encounters when the type is known-and-decoded or unknown-and-ignored.
 func TestParsedTypes(t *testing.T) {
 	const (
-		firstReqType  = 0
-		knownType     = 1
-		unknownType   = 3
-		secondReqType = 4
+		knownType       = 1
+		unknownType     = 3
+		secondKnownType = 4
 	)
 
 	tests := []parsedTypeTest{
 		{
-			name:   "known optional and unknown optional",
+			name:   "known and unknown",
 			encode: []tlv.Type{knownType, unknownType},
 			decode: []tlv.Type{knownType},
+			expParsedTypes: tlv.TypeSet{
+				unknownType: false,
+				knownType:   true,
+			},
 		},
 		{
-			name:   "unknown required and known optional",
-			encode: []tlv.Type{firstReqType, knownType},
-			decode: []tlv.Type{knownType},
-			expErr: tlv.ErrUnknownRequiredType(firstReqType),
-		},
-		{
-			name:   "unknown required and unknown optional",
-			encode: []tlv.Type{unknownType, secondReqType},
-			expErr: tlv.ErrUnknownRequiredType(secondReqType),
-		},
-		{
-			name:   "unknown required and known required",
-			encode: []tlv.Type{firstReqType, secondReqType},
-			decode: []tlv.Type{secondReqType},
-			expErr: tlv.ErrUnknownRequiredType(firstReqType),
-		},
-		{
-			name:   "two unknown required",
-			encode: []tlv.Type{firstReqType, secondReqType},
-			expErr: tlv.ErrUnknownRequiredType(firstReqType),
+			name:   "known and missing known",
+			encode: []tlv.Type{knownType},
+			decode: []tlv.Type{knownType, secondKnownType},
+			expParsedTypes: tlv.TypeSet{
+				knownType: true,
+			},
 		},
 	}
 
@@ -92,16 +81,10 @@ func testParsedTypes(t *testing.T, test parsedTypeTest) {
 	parsedTypes, err := decStream.DecodeWithParsedTypes(
 		bytes.NewReader(b.Bytes()),
 	)
-	if !reflect.DeepEqual(err, test.expErr) {
-		t.Fatalf("error mismatch, want: %v got: %v", err, test.expErr)
+	if err != nil {
+		t.Fatalf("error decoding: %v", err)
 	}
-
-	// Assert that all encoded types are included in the set of parsed
-	// types.
-	for _, typ := range test.encode {
-		if _, ok := parsedTypes[typ]; !ok {
-			t.Fatalf("encoded type %d should be in parsed types",
-				typ)
-		}
+	if !reflect.DeepEqual(parsedTypes, test.expParsedTypes) {
+		t.Fatalf("error mismatch on parsed types")
 	}
 }
diff --git a/tlv/tlv_test.go b/tlv/tlv_test.go
index 13ef24685..e5d473413 100644
--- a/tlv/tlv_test.go
+++ b/tlv/tlv_test.go
@@ -203,26 +203,6 @@ var tlvDecodingFailureTests = []struct {
 		},
 		expErr: io.ErrUnexpectedEOF,
 	},
-	{
-		name:   "unknown even type",
-		bytes:  []byte{0x12, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x12),
-	},
-	{
-		name:   "unknown even type",
-		bytes:  []byte{0xfd, 0x01, 0x02, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x102),
-	},
-	{
-		name:   "unknown even type",
-		bytes:  []byte{0xfe, 0x01, 0x00, 0x00, 0x02, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x01000002),
-	},
-	{
-		name:   "unknown even type",
-		bytes:  []byte{0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x0100000000000002),
-	},
 	{
 		name:   "greater than encoding length for n1's amt",
 		bytes:  []byte{0x01, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
@@ -340,12 +320,6 @@ var tlvDecodingFailureTests = []struct {
 		expErr: tlv.NewTypeForDecodingErr(new(nodeAmts), "nodeAmts", 50, 49),
 		skipN2: true,
 	},
-	{
-		name:   "unknown required type or n1",
-		bytes:  []byte{0x00, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x00),
-		skipN2: true,
-	},
 	{
 		name:   "less than encoding length for n1's cltvDelta",
 		bytes:  []byte{0xfd, 0x00, 0x0fe, 0x00},
@@ -364,12 +338,6 @@ var tlvDecodingFailureTests = []struct {
 		expErr: tlv.NewTypeForDecodingErr(new(uint16), "uint16", 3, 2),
 		skipN2: true,
 	},
-	{
-		name:   "unknown even field for n1's namespace",
-		bytes:  []byte{0x0a, 0x00},
-		expErr: tlv.ErrUnknownRequiredType(0x0a),
-		skipN2: true,
-	},
 	{
 		name: "valid records but invalid ordering",
 		bytes: []byte{0x02, 0x08,