decred.org/dcrdex@v1.0.5/client/db/types.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package db
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"math"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"decred.org/dcrdex/client/asset"
    16  	"decred.org/dcrdex/dex"
    17  	"decred.org/dcrdex/dex/config"
    18  	"decred.org/dcrdex/dex/encode"
    19  	"decred.org/dcrdex/dex/order"
    20  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    21  	"golang.org/x/crypto/blake2s"
    22  )
    23  
    24  // Severity indicates the level of required action for a notification. The DEX
    25  // db only stores notifications with Severity >= Success.
    26  type Severity uint8
    27  
    28  const (
    29  	Ignorable Severity = iota
    30  	// Data notifications are not meant for display to the user. These
    31  	// notifications are used only for communication of information necessary for
    32  	// UI updates or other high-level state changes.
    33  	Data
    34  	// Poke notifications are not persistent across sessions. These should be
    35  	// displayed if the user has a live notification feed. They are not stored in
    36  	// the database.
    37  	Poke
    38  	// Success and higher are stored and can be recalled using DB.NotificationsN.
    39  	Success
    40  	WarningLevel
    41  	ErrorLevel
    42  )
    43  
    44  const (
    45  	ErrNoCredentials = dex.ErrorKind("no credentials have been stored")
    46  	ErrAcctNotFound  = dex.ErrorKind("account not found")
    47  	ErrNoSeedGenTime = dex.ErrorKind("seed generation time has not been stored")
    48  )
    49  
    50  // String satisfies fmt.Stringer for Severity.
    51  func (s Severity) String() string {
    52  	switch s {
    53  	case Ignorable:
    54  		return "ignore"
    55  	case Data:
    56  		return "data"
    57  	case Poke:
    58  		return "poke"
    59  	case WarningLevel:
    60  		return "warning"
    61  	case ErrorLevel:
    62  		return "error"
    63  	case Success:
    64  		return "success"
    65  	}
    66  	return "unknown severity"
    67  }
    68  
    69  // PrimaryCredentials should be created during app initialization. Both the seed
    70  // and the inner key (and technically the other two fields) should be generated
    71  // with a cryptographically-secure prng.
    72  type PrimaryCredentials struct {
    73  	// EncSeed is the root seed used to create a hierarchical deterministic
    74  	// key chain (see also dcrd/hdkeychain.NewMaster/ExtendedKey).
    75  	EncSeed []byte
    76  	// EncInnerKey is an encrypted encryption key. The inner key will never
    77  	// change. The inner key is encrypted with the outer key, which itself is
    78  	// based on the user's password.
    79  	EncInnerKey []byte
    80  	// InnerKeyParams are the key parameters for the inner key.
    81  	InnerKeyParams []byte
    82  	// OuterKeyParams are the key parameters for the outer key.
    83  	OuterKeyParams []byte
    84  	// Birthday is the time stamp associated with seed creation.
    85  	Birthday time.Time
    86  	// Version is the current PrimaryCredentials version.
    87  	Version uint16
    88  }
    89  
    90  // when updating to bonds, default to 42 (DCR)
    91  const defaultBondAsset = 42
    92  
    93  // BondUID generates a unique identifier from a bond's asset ID and coin ID.
    94  func BondUID(assetID uint32, bondCoinID []byte) []byte {
    95  	return hashKey(append(uint32Bytes(assetID), bondCoinID...))
    96  }
    97  
    98  // Bond is stored in a sub-bucket of an account bucket. The dex.Bytes type is
    99  // used for certain fields so that the data marshals to/from hexadecimal.
   100  type Bond struct {
   101  	Version    uint16    `json:"ver"`
   102  	AssetID    uint32    `json:"asset"`
   103  	CoinID     dex.Bytes `json:"coinID"`
   104  	UnsignedTx dex.Bytes `json:"utx"`
   105  	SignedTx   dex.Bytes `json:"stx"`  // can be obtained from msgjson.Bond.CoinID
   106  	Data       dex.Bytes `json:"data"` // e.g. redeem script
   107  	Amount     uint64    `json:"amt"`
   108  	LockTime   uint64    `json:"lockTime"`
   109  	KeyIndex   uint32    `json:"keyIndex"` // child key index for HD path: m / hdKeyPurposeBonds / assetID' / bondIndex
   110  	RefundTx   dex.Bytes `json:"refundTx"` // pays to wallet that created it - only a backup for emergency!
   111  
   112  	Confirmed bool `json:"confirmed"` // if reached required confs according to server, not in serialization
   113  	Refunded  bool `json:"refunded"`  // not in serialization
   114  
   115  	Strength uint32 `json:"strength"`
   116  }
   117  
   118  // UniqueID computes the bond's unique ID for keying purposes.
   119  func (b *Bond) UniqueID() []byte {
   120  	return BondUID(b.AssetID, b.CoinID)
   121  }
   122  
   123  // Encode serialized the Bond. Confirmed and Refund are not included.
   124  func (b *Bond) Encode() []byte {
   125  	return versionedBytes(2).
   126  		AddData(uint16Bytes(b.Version)).
   127  		AddData(uint32Bytes(b.AssetID)).
   128  		AddData(b.CoinID).
   129  		AddData(b.UnsignedTx).
   130  		AddData(b.SignedTx).
   131  		AddData(b.Data).
   132  		AddData(uint64Bytes(b.Amount)).
   133  		AddData(uint64Bytes(b.LockTime)).
   134  		AddData(uint32Bytes(b.KeyIndex)).
   135  		AddData(b.RefundTx).
   136  		AddData(uint32Bytes(b.Strength))
   137  	// Confirmed and Refunded are not part of the encoding.
   138  }
   139  
   140  // DecodeBond decodes the versioned blob into a *Bond.
   141  func DecodeBond(b []byte) (*Bond, error) {
   142  	ver, pushes, err := encode.DecodeBlob(b)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	switch ver {
   147  	case 0:
   148  		return decodeBond_v0(pushes)
   149  	case 1:
   150  		return decodeBond_v1(pushes)
   151  	case 2:
   152  		return decodeBond_v2(pushes)
   153  	}
   154  	return nil, fmt.Errorf("unknown Bond version %d", ver)
   155  }
   156  
   157  // decodeBond_v0 handles the unreleased v0 db.Bond format that did spend some
   158  // notable time on master. The app will recognize the special KeyIndex value and
   159  // use the RefundTx instead, if there were were any unspent bonds at time of
   160  // upgrade, but this is mainly so we can decode the v0 blobs.
   161  func decodeBond_v0(pushes [][]byte) (*Bond, error) {
   162  	if len(pushes) != 10 {
   163  		return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes))
   164  	}
   165  	ver, assetIDB, coinID := pushes[0], pushes[1], pushes[2]
   166  	utx, stx := pushes[3], pushes[4]
   167  	data, amtB, lockTimeB := pushes[5], pushes[6], pushes[7]
   168  	// privKey := pushes[8] // in v0, so we will use the refundTx to handle this unreleased revision without deleting out DB files
   169  	refundTx := pushes[9]
   170  	return &Bond{
   171  		Version:    intCoder.Uint16(ver),
   172  		AssetID:    intCoder.Uint32(assetIDB),
   173  		CoinID:     coinID,
   174  		UnsignedTx: utx,
   175  		SignedTx:   stx,
   176  		Data:       data,
   177  		Amount:     intCoder.Uint64(amtB),
   178  		LockTime:   intCoder.Uint64(lockTimeB),
   179  		KeyIndex:   math.MaxUint32, // special
   180  		RefundTx:   refundTx,
   181  	}, nil
   182  }
   183  
   184  func decodeBond_v1(pushes [][]byte) (*Bond, error) {
   185  	if len(pushes) != 10 {
   186  		return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes))
   187  	}
   188  	return decodeBond_v2(append(pushes, []byte{0, 0, 0, 0} /* uint32 strength */))
   189  }
   190  
   191  func decodeBond_v2(pushes [][]byte) (*Bond, error) {
   192  	if len(pushes) != 11 {
   193  		return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes))
   194  	}
   195  	ver, assetIDB, coinID := pushes[0], pushes[1], pushes[2]
   196  	utx, stx := pushes[3], pushes[4]
   197  	data, amtB, lockTimeB := pushes[5], pushes[6], pushes[7]
   198  	keyIndex, refundTx, strength := pushes[8], pushes[9], pushes[10]
   199  	return &Bond{
   200  		Version:    intCoder.Uint16(ver),
   201  		AssetID:    intCoder.Uint32(assetIDB),
   202  		CoinID:     coinID,
   203  		UnsignedTx: utx,
   204  		SignedTx:   stx,
   205  		Data:       data,
   206  		Amount:     intCoder.Uint64(amtB),
   207  		LockTime:   intCoder.Uint64(lockTimeB),
   208  		KeyIndex:   intCoder.Uint32(keyIndex),
   209  		RefundTx:   refundTx,
   210  		Strength:   intCoder.Uint32(strength),
   211  	}, nil
   212  }
   213  
   214  // AccountInfo is information about an account on a Decred DEX. The database
   215  // is designed for one account per server.
   216  type AccountInfo struct {
   217  	// Host, Cert, and DEXPubKey identify the DEX server.
   218  	Host      string
   219  	Cert      []byte
   220  	DEXPubKey *secp256k1.PublicKey
   221  
   222  	// EncKeyV2 is an encrypted private key generated deterministically from the
   223  	// app seed.
   224  	EncKeyV2 []byte
   225  	// LegacyEncKey is an old-style non-hierarchical key that must be included
   226  	// when exporting the client credentials, since it cannot be regenerated
   227  	// automatically.
   228  	LegacyEncKey []byte
   229  
   230  	Bonds        []*Bond
   231  	TargetTier   uint64 // zero means no bond maintenance (allows actual tier to drop negative)
   232  	MaxBondedAmt uint64
   233  	PenaltyComps uint16
   234  	BondAsset    uint32 // the asset to use when auto-posting bonds
   235  	Disabled     bool   // whether the account is disabled
   236  
   237  	// DEPRECATED reg fee data. Bond txns are in a sub-bucket.
   238  	// Left until we need to upgrade just for serialization simplicity.
   239  	LegacyFeeCoin    []byte
   240  	LegacyFeeAssetID uint32
   241  	LegacyFeePaid    bool
   242  }
   243  
   244  // Encode the AccountInfo as bytes. NOTE: remove deprecated fee fields and do a
   245  // DB upgrade at some point. But how to deal with old accounts needing to store
   246  // this data forever?
   247  func (ai *AccountInfo) Encode() []byte {
   248  	return versionedBytes(4).
   249  		AddData([]byte(ai.Host)).
   250  		AddData(ai.Cert).
   251  		AddData(ai.DEXPubKey.SerializeCompressed()).
   252  		AddData(ai.EncKeyV2).
   253  		AddData(ai.LegacyEncKey).
   254  		AddData(encode.Uint64Bytes(ai.TargetTier)).
   255  		AddData(encode.Uint64Bytes(ai.MaxBondedAmt)).
   256  		AddData(encode.Uint32Bytes(ai.BondAsset)).
   257  		AddData(encode.Uint32Bytes(ai.LegacyFeeAssetID)).
   258  		AddData(ai.LegacyFeeCoin).
   259  		AddData(encode.Uint16Bytes(ai.PenaltyComps))
   260  }
   261  
   262  // ViewOnly is true if account keys are not saved.
   263  func (ai *AccountInfo) ViewOnly() bool {
   264  	return len(ai.EncKey()) == 0
   265  }
   266  
   267  // EncKey is the encrypted account private key.
   268  func (ai *AccountInfo) EncKey() []byte {
   269  	if len(ai.EncKeyV2) > 0 {
   270  		return ai.EncKeyV2
   271  	}
   272  	return ai.LegacyEncKey
   273  }
   274  
   275  // DecodeAccountInfo decodes the versioned blob into an *AccountInfo. The byte
   276  // slice fields of AccountInfo reference the underlying buffer of the the input.
   277  func DecodeAccountInfo(b []byte) (*AccountInfo, error) {
   278  	ver, pushes, err := encode.DecodeBlob(b)
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  	switch ver {
   283  	case 0:
   284  		return decodeAccountInfo_v0(pushes) // caller must decode account proof
   285  	case 1:
   286  		return decodeAccountInfo_v1(pushes)
   287  	case 2:
   288  		return decodeAccountInfo_v2(pushes)
   289  	case 3:
   290  		return decodeAccountInfo_v3(pushes)
   291  	case 4:
   292  		return decodeAccountInfo_v4(pushes)
   293  	}
   294  	return nil, fmt.Errorf("unknown AccountInfo version %d", ver)
   295  }
   296  
   297  func decodeAccountInfo_v0(pushes [][]byte) (*AccountInfo, error) {
   298  	return decodeAccountInfo_v1(append(pushes, nil))
   299  }
   300  
   301  func decodeAccountInfo_v1(pushes [][]byte) (*AccountInfo, error) {
   302  	if len(pushes) != 6 {
   303  		return nil, fmt.Errorf("decodeAccountInfo: expected 6 data pushes, got %d", len(pushes))
   304  	}
   305  	hostB, legacyKeyB, dexB := pushes[0], pushes[1], pushes[2]
   306  	coinB, certB, v2Key := pushes[3], pushes[4], pushes[5]
   307  	pk, err := secp256k1.ParsePubKey(dexB)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	return &AccountInfo{
   312  		Host:             string(hostB),
   313  		Cert:             certB,
   314  		DEXPubKey:        pk,
   315  		EncKeyV2:         v2Key,
   316  		LegacyEncKey:     legacyKeyB,
   317  		LegacyFeeAssetID: 42, // only option at this version
   318  		LegacyFeeCoin:    coinB,
   319  		// LegacyFeePaid comes from AccountProof.
   320  	}, nil
   321  }
   322  
   323  func decodeAccountInfo_v2(pushes [][]byte) (*AccountInfo, error) {
   324  	if len(pushes) != 7 {
   325  		return nil, fmt.Errorf("decodeAccountInfo: expected 7 data pushes, got %d", len(pushes))
   326  	}
   327  	hostB, certB, dexPkB := pushes[0], pushes[1], pushes[2] // dex identity
   328  	v2Key, legacyKeyB := pushes[3], pushes[4]               // account identity
   329  	regAssetB, coinB := pushes[5], pushes[6]                // legacy reg fee data
   330  	pk, err := secp256k1.ParsePubKey(dexPkB)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	return &AccountInfo{
   335  		Host:         string(hostB),
   336  		Cert:         certB,
   337  		DEXPubKey:    pk,
   338  		EncKeyV2:     v2Key,
   339  		LegacyEncKey: legacyKeyB,
   340  		// Bonds decoded by DecodeBond from separate pushes.
   341  		BondAsset:        defaultBondAsset,
   342  		LegacyFeeAssetID: intCoder.Uint32(regAssetB),
   343  		LegacyFeeCoin:    coinB, // NOTE: no longer in current serialization.
   344  		// LegacyFeePaid comes from AccountProof.
   345  	}, nil
   346  }
   347  
   348  func decodeAccountInfo_v3(pushes [][]byte) (*AccountInfo, error) {
   349  	if len(pushes) != 10 {
   350  		return nil, fmt.Errorf("decodeAccountInfo_v3: expected 10 data pushes, got %d", len(pushes))
   351  	}
   352  	pushes = append(pushes, []byte{0, 0}) // 16-bit PenaltyComps
   353  	return decodeAccountInfo_v4(pushes)
   354  }
   355  
   356  func decodeAccountInfo_v4(pushes [][]byte) (*AccountInfo, error) {
   357  	if len(pushes) != 11 {
   358  		return nil, fmt.Errorf("decodeAccountInfo: expected 11 data pushes, got %d", len(pushes))
   359  	}
   360  	hostB, certB, dexPkB := pushes[0], pushes[1], pushes[2]                // dex identity
   361  	v2Key, legacyKeyB := pushes[3], pushes[4]                              // account identity
   362  	targetTierB, maxBondedB, bondAssetB := pushes[5], pushes[6], pushes[7] // bond options
   363  	regAssetB, coinB, penaltyComps := pushes[8], pushes[9], pushes[10]     // legacy reg fee data
   364  	pk, err := secp256k1.ParsePubKey(dexPkB)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	return &AccountInfo{
   369  		Host:         string(hostB),
   370  		Cert:         certB,
   371  		DEXPubKey:    pk,
   372  		EncKeyV2:     v2Key,
   373  		LegacyEncKey: legacyKeyB,
   374  		// Bonds decoded by DecodeBond from separate pushes.
   375  		TargetTier:       intCoder.Uint64(targetTierB),
   376  		MaxBondedAmt:     intCoder.Uint64(maxBondedB),
   377  		PenaltyComps:     intCoder.Uint16(penaltyComps),
   378  		BondAsset:        intCoder.Uint32(bondAssetB),
   379  		LegacyFeeAssetID: intCoder.Uint32(regAssetB),
   380  		LegacyFeeCoin:    coinB, // NOTE: no longer in current serialization.
   381  		// LegacyFeePaid comes from AccountProof.
   382  	}, nil
   383  }
   384  
   385  // AccountProof is information necessary to prove that the DEX server accepted
   386  // the account's fee payment. The fee coin is not part of the proof, since it
   387  // is already stored as part of the AccountInfo blob. DEPRECATED.
   388  type AccountProof struct{}
   389  
   390  // Encode encodes the AccountProof to a versioned blob.
   391  func (p *AccountProof) Encode() []byte {
   392  	return versionedBytes(1)
   393  }
   394  
   395  // DecodeAccountProof decodes the versioned blob to a *MatchProof.
   396  func DecodeAccountProof(b []byte) (*AccountProof, error) {
   397  	return &AccountProof{}, nil
   398  }
   399  
   400  // MetaOrder is an order and its metadata.
   401  type MetaOrder struct {
   402  	// MetaData is important auxiliary information about the order.
   403  	MetaData *OrderMetaData
   404  	// Order is the order.
   405  	Order order.Order
   406  }
   407  
   408  // OrderMetaData is important auxiliary information about an order.
   409  type OrderMetaData struct {
   410  	// Status is the last known order status.
   411  	Status order.OrderStatus
   412  	// Host is the hostname of the server that this order is associated with.
   413  	Host string
   414  	// Proof is the signatures and other verification-related data for the order.
   415  	Proof OrderProof
   416  	// ChangeCoin is a change coin from a match. Change coins are "daisy-chained"
   417  	// for matches. All funding coins go into the first match, and the change coin
   418  	// from the initiation transaction is used to fund the next match. The
   419  	// change from that matches ini tx funds the next match, etc.
   420  	ChangeCoin order.CoinID
   421  	// LinkedOrder is used to specify the cancellation order for a trade, or
   422  	// vice-versa.
   423  	LinkedOrder order.OrderID
   424  	// SwapFeesPaid is the sum of the actual fees paid for all swaps.
   425  	SwapFeesPaid uint64
   426  	// RedemptionFeesPaid is the sum of the actual fees paid for all
   427  	// redemptions.
   428  	RedemptionFeesPaid uint64
   429  	// FundingFeesPaid is the fees paid when funding the order. This is > 0
   430  	// when funding the order required a split tx.
   431  	FundingFeesPaid uint64
   432  
   433  	// EpochDur is the epoch duration for the market at the time the order was
   434  	// submitted. When considered with the order's ServerTime, we also know the
   435  	// epoch index, which is helpful for determining if an epoch order should
   436  	// have been matched. WARNING: may load from DB as zero for older orders, in
   437  	// which case the current asset config should be used.
   438  	EpochDur uint64
   439  
   440  	// We store any variable information of each dex.Asset (the server's asset
   441  	// config at time of order). This includes: the max fee rates for swap and
   442  	// redeem, and the asset versions, and the required swap confirmations
   443  	// counts.
   444  
   445  	// FromSwapConf and ToSwapConf are the dex.Asset.SwapConf values at the time
   446  	// the order is submitted. WARNING: may load from DB as zero for older
   447  	// orders, in which case the current asset config should be used.
   448  	FromSwapConf uint32
   449  	ToSwapConf   uint32
   450  	// MaxFeeRate is the dex.Asset.MaxFeeRate at the time of ordering. The rates
   451  	// assigned to matches will be validated against this value.
   452  	MaxFeeRate uint64
   453  	// RedeemMaxFeeRate is the dex.Asset.MaxFeeRate for the redemption asset at
   454  	// the time of ordering. This rate is used to reserve funds for redemption,
   455  	// and therefore this rate can be used when actually submitting a redemption
   456  	// transaction.
   457  	RedeemMaxFeeRate uint64
   458  	// FromVersion is the version of the from asset.
   459  	FromVersion uint32
   460  	// ToVersion is the version of the to asset.
   461  	ToVersion uint32
   462  
   463  	// Options are the options offered by the wallet and selected by the user.
   464  	Options map[string]string
   465  	// RedemptionReserves is the amount of funds reserved by the wallet to pay
   466  	// the transaction fees for all the possible redemptions in this order.
   467  	// The amount that should be locked at any point can be determined by
   468  	// checking the status of the order and the status of all matches related
   469  	// to this order, and determining how many more possible redemptions there
   470  	// could be.
   471  	RedemptionReserves uint64
   472  	// RedemptionRefunds is the amount of funds reserved by the wallet to pay
   473  	// the transaction fees for all the possible refunds in this order.
   474  	// The amount that should be locked at any point can be determined by
   475  	// checking the status of the order and the status of all matches related
   476  	// to this order, and determining how many more possible refunds there
   477  	// could be.
   478  	RefundReserves uint64
   479  	// AccelerationCoins keeps track of all the change coins generated from doing
   480  	// accelerations on this order.
   481  	AccelerationCoins []order.CoinID
   482  }
   483  
   484  // MetaMatch is a match and its metadata.
   485  type MetaMatch struct {
   486  	// UserMatch is the match info.
   487  	*order.UserMatch
   488  	// MetaData is important auxiliary information about the match.
   489  	MetaData *MatchMetaData
   490  }
   491  
   492  // MatchOrderUniqueID is a unique ID for the match-order pair.
   493  func (m *MetaMatch) MatchOrderUniqueID() []byte {
   494  	return hashKey(append(m.MatchID[:], m.OrderID[:]...))
   495  }
   496  
   497  // MatchIsActive returns false (i.e. the match is inactive) if any: (1) status
   498  // is MatchConfirmed OR InitSig unset, signaling a cancel order match, which is
   499  // never active, (2) the match is refunded, or (3) it is revoked and this side
   500  // of the match requires no further action like refund or auto-redeem.
   501  func MatchIsActive(match *order.UserMatch, proof *MatchProof) bool {
   502  	// MatchComplete only means inactive if: (a) cancel order match or (b) the
   503  	// redeem request was accepted for trade orders. A cancel order match starts
   504  	// complete and has no InitSig as there is no swap negotiation.
   505  	// Unfortunately, an empty Address is not sufficient since taker cancel
   506  	// matches included the makers Address.
   507  	if match.Status == order.MatchConfirmed {
   508  		return false
   509  	}
   510  
   511  	// Cancel match
   512  	if match.Status == order.MatchComplete && len(proof.Auth.InitSig) == 0 {
   513  		return false
   514  	}
   515  
   516  	// Refunded matches are inactive regardless of status.
   517  	if len(proof.RefundCoin) > 0 {
   518  		return false
   519  	}
   520  
   521  	// Revoked matches may need to be refunded or auto-redeemed first.
   522  	if proof.IsRevoked() {
   523  		// - NewlyMatched requires no further action from either side
   524  		// - MakerSwapCast requires no further action from the taker
   525  		// - (TakerSwapCast requires action on both sides)
   526  		// Matches that make it to MakerRedeemed or MatchComplete must
   527  		// stay active until the redeem is confirmed. When the redeem
   528  		// is confirmed is up to the asset and differs between assets.
   529  		status, side := match.Status, match.Side
   530  		if status == order.NewlyMatched ||
   531  			(status == order.MakerSwapCast && side == order.Taker) {
   532  			return false
   533  		}
   534  	}
   535  	return true
   536  }
   537  
   538  // MatchIsActiveV6Upgrade is the previous version of MatchIsActive that is
   539  // required for the V6 upgrade of the DB.
   540  func MatchIsActiveV6Upgrade(match *order.UserMatch, proof *MatchProof) bool {
   541  	// MatchComplete only means inactive if: (a) cancel order match or (b) the
   542  	// redeem request was accepted for trade orders. A cancel order match starts
   543  	// complete and has no InitSig as their is no swap negotiation.
   544  	// Unfortunately, an empty Address is not sufficient since taker cancel
   545  	// matches included the makers Address.
   546  	if match.Status == order.MatchComplete && (len(proof.Auth.RedeemSig) > 0 || // completed trade
   547  		len(proof.Auth.InitSig) == 0) { // completed cancel
   548  		return false
   549  	}
   550  
   551  	// Refunded matches are inactive regardless of status.
   552  	if len(proof.RefundCoin) > 0 {
   553  		return false
   554  	}
   555  
   556  	// Revoked matches may need to be refunded or auto-redeemed first.
   557  	if proof.IsRevoked() {
   558  		// - NewlyMatched requires no further action from either side
   559  		// - MakerSwapCast requires no further action from the taker
   560  		// - (TakerSwapCast requires action on both sides)
   561  		// - MakerRedeemed requires no further action from the maker
   562  		// - MatchComplete requires no further action. This happens if taker
   563  		//   does not have server's ack of their redeem request (RedeemSig).
   564  		status, side := match.Status, match.Side
   565  		if status == order.NewlyMatched || status == order.MatchComplete ||
   566  			(status == order.MakerSwapCast && side == order.Taker) ||
   567  			(status == order.MakerRedeemed && side == order.Maker) {
   568  			return false
   569  		}
   570  	}
   571  	return true
   572  }
   573  
   574  // MatchMetaData is important auxiliary information about the match.
   575  type MatchMetaData struct {
   576  	// Proof is the signatures and other verification-related data for the match.
   577  	Proof MatchProof
   578  	// DEX is the URL of the server that this match is associated with.
   579  	DEX string
   580  	// Base is the base asset of the exchange market.
   581  	Base uint32
   582  	// Quote is the quote asset of the exchange market.
   583  	Quote uint32
   584  	// Stamp is the match time (ms UNIX), according to the server's 'match'
   585  	// request timestamp.
   586  	Stamp uint64
   587  	// TODO: ReceiveTime uint64 -- local time stamp for match age and time display
   588  }
   589  
   590  // MatchAuth holds the DEX signatures and timestamps associated with the
   591  // messages in the negotiation process.
   592  type MatchAuth struct {
   593  	MatchSig        []byte
   594  	MatchStamp      uint64
   595  	InitSig         []byte
   596  	InitStamp       uint64
   597  	AuditSig        []byte
   598  	AuditStamp      uint64
   599  	RedeemSig       []byte
   600  	RedeemStamp     uint64
   601  	RedemptionSig   []byte
   602  	RedemptionStamp uint64
   603  }
   604  
   605  // MatchProof is information related to the progression of the swap negotiation
   606  // process.
   607  type MatchProof struct {
   608  	ContractData    []byte
   609  	CounterContract []byte
   610  	CounterTxData   []byte
   611  	SecretHash      []byte
   612  	Secret          []byte
   613  	MakerSwap       order.CoinID
   614  	MakerRedeem     order.CoinID
   615  	TakerSwap       order.CoinID
   616  	TakerRedeem     order.CoinID
   617  	RefundCoin      order.CoinID
   618  	Auth            MatchAuth
   619  	ServerRevoked   bool
   620  	SelfRevoked     bool
   621  	// SwapFeeConfirmed indicate the fees for this match have been
   622  	// confirmed and the value added to the trade.
   623  	SwapFeeConfirmed bool
   624  	// RedemptionFeeConfirmed indicate the fees for this match have been
   625  	// confirmed and the value added to the trade.
   626  	RedemptionFeeConfirmed bool
   627  }
   628  
   629  func boolByte(b bool) []byte {
   630  	if b {
   631  		return []byte{1}
   632  	}
   633  	return []byte{0}
   634  }
   635  
   636  // MatchProofVer is the current serialization version of a MatchProof.
   637  const (
   638  	MatchProofVer    = 3
   639  	matchProofPushes = 24
   640  )
   641  
   642  // Encode encodes the MatchProof to a versioned blob.
   643  func (p *MatchProof) Encode() []byte {
   644  	auth := p.Auth
   645  	return versionedBytes(MatchProofVer).
   646  		AddData(p.ContractData).
   647  		AddData(p.CounterContract).
   648  		AddData(p.SecretHash).
   649  		AddData(p.Secret).
   650  		AddData(p.MakerSwap).
   651  		AddData(p.MakerRedeem).
   652  		AddData(p.TakerSwap).
   653  		AddData(p.TakerRedeem).
   654  		AddData(p.RefundCoin).
   655  		AddData(auth.MatchSig).
   656  		AddData(uint64Bytes(auth.MatchStamp)).
   657  		AddData(auth.InitSig).
   658  		AddData(uint64Bytes(auth.InitStamp)).
   659  		AddData(auth.AuditSig).
   660  		AddData(uint64Bytes(auth.AuditStamp)).
   661  		AddData(auth.RedeemSig).
   662  		AddData(uint64Bytes(auth.RedeemStamp)).
   663  		AddData(auth.RedemptionSig).
   664  		AddData(uint64Bytes(auth.RedemptionStamp)).
   665  		AddData(boolByte(p.ServerRevoked)).
   666  		AddData(boolByte(p.SelfRevoked)).
   667  		AddData(p.CounterTxData).
   668  		AddData(boolByte(p.SwapFeeConfirmed)).
   669  		AddData(boolByte(p.RedemptionFeeConfirmed))
   670  }
   671  
   672  // DecodeMatchProof decodes the versioned blob to a *MatchProof.
   673  func DecodeMatchProof(b []byte) (*MatchProof, uint8, error) {
   674  	ver, pushes, err := encode.DecodeBlob(b, matchProofPushes)
   675  	if err != nil {
   676  		return nil, 0, err
   677  	}
   678  	switch ver {
   679  	case 3: // MatchProofVer
   680  		proof, err := decodeMatchProof_v3(pushes)
   681  		return proof, ver, err
   682  	case 2:
   683  		proof, err := decodeMatchProof_v2(pushes)
   684  		return proof, ver, err
   685  	case 1:
   686  		proof, err := decodeMatchProof_v1(pushes)
   687  		return proof, ver, err
   688  	case 0:
   689  		proof, err := decodeMatchProof_v0(pushes)
   690  		return proof, ver, err
   691  	}
   692  	return nil, ver, fmt.Errorf("unknown MatchProof version %d", ver)
   693  }
   694  
   695  func decodeMatchProof_v0(pushes [][]byte) (*MatchProof, error) {
   696  	pushes = append(pushes, encode.ByteFalse)
   697  	return decodeMatchProof_v1(pushes)
   698  }
   699  
   700  func decodeMatchProof_v1(pushes [][]byte) (*MatchProof, error) {
   701  	pushes = append(pushes, nil)
   702  	return decodeMatchProof_v2(pushes)
   703  }
   704  
   705  func decodeMatchProof_v2(pushes [][]byte) (*MatchProof, error) {
   706  	// Add the MatchProof SwapFeeConfirmed and RedemptionFeeConfirmed bytes.
   707  	// True because all fees until now are confirmed.
   708  	pushes = append(pushes, encode.ByteTrue, encode.ByteTrue)
   709  	return decodeMatchProof_v3(pushes)
   710  }
   711  
   712  func decodeMatchProof_v3(pushes [][]byte) (*MatchProof, error) {
   713  	if len(pushes) != matchProofPushes {
   714  		return nil, fmt.Errorf("DecodeMatchProof: expected %d pushes, got %d",
   715  			matchProofPushes, len(pushes))
   716  	}
   717  	return &MatchProof{
   718  		ContractData:    pushes[0],
   719  		CounterContract: pushes[1],
   720  		CounterTxData:   pushes[21],
   721  		SecretHash:      pushes[2],
   722  		Secret:          pushes[3],
   723  		MakerSwap:       pushes[4],
   724  		MakerRedeem:     pushes[5],
   725  		TakerSwap:       pushes[6],
   726  		TakerRedeem:     pushes[7],
   727  		RefundCoin:      pushes[8],
   728  		Auth: MatchAuth{
   729  			MatchSig:        pushes[9],
   730  			MatchStamp:      intCoder.Uint64(pushes[10]),
   731  			InitSig:         pushes[11],
   732  			InitStamp:       intCoder.Uint64(pushes[12]),
   733  			AuditSig:        pushes[13],
   734  			AuditStamp:      intCoder.Uint64(pushes[14]),
   735  			RedeemSig:       pushes[15],
   736  			RedeemStamp:     intCoder.Uint64(pushes[16]),
   737  			RedemptionSig:   pushes[17],
   738  			RedemptionStamp: intCoder.Uint64(pushes[18]),
   739  		},
   740  		ServerRevoked:          bytes.Equal(pushes[19], encode.ByteTrue),
   741  		SelfRevoked:            bytes.Equal(pushes[20], encode.ByteTrue),
   742  		SwapFeeConfirmed:       bytes.Equal(pushes[21], encode.ByteTrue),
   743  		RedemptionFeeConfirmed: bytes.Equal(pushes[22], encode.ByteTrue),
   744  	}, nil
   745  }
   746  
   747  // IsRevoked is true if either ServerRevoked or SelfRevoked is true.
   748  func (p *MatchProof) IsRevoked() bool {
   749  	return p.ServerRevoked || p.SelfRevoked
   750  }
   751  
   752  // OrderProof is information related to order authentication and matching.
   753  type OrderProof struct {
   754  	DEXSig   []byte
   755  	Preimage []byte
   756  }
   757  
   758  // Encode encodes the OrderProof to a versioned blob.
   759  func (p *OrderProof) Encode() []byte {
   760  	return versionedBytes(0).AddData(p.DEXSig).AddData(p.Preimage)
   761  }
   762  
   763  // DecodeOrderProof decodes the versioned blob to an *OrderProof.
   764  func DecodeOrderProof(b []byte) (*OrderProof, error) {
   765  	ver, pushes, err := encode.DecodeBlob(b)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	switch ver {
   770  	case 0:
   771  		return decodeOrderProof_v0(pushes)
   772  	}
   773  	return nil, fmt.Errorf("unknown OrderProof version %d", ver)
   774  }
   775  
   776  func decodeOrderProof_v0(pushes [][]byte) (*OrderProof, error) {
   777  	if len(pushes) != 2 {
   778  		return nil, fmt.Errorf("decodeOrderProof: expected 2 push, got %d", len(pushes))
   779  	}
   780  	return &OrderProof{
   781  		DEXSig:   pushes[0],
   782  		Preimage: pushes[1],
   783  	}, nil
   784  }
   785  
   786  // encodeAssetBalance serializes an asset.Balance.
   787  func encodeAssetBalance(bal *asset.Balance) []byte {
   788  	return versionedBytes(0).
   789  		AddData(uint64Bytes(bal.Available)).
   790  		AddData(uint64Bytes(bal.Immature)).
   791  		AddData(uint64Bytes(bal.Locked))
   792  }
   793  
   794  // decodeAssetBalance deserializes an asset.Balance.
   795  func decodeAssetBalance(b []byte) (*asset.Balance, error) {
   796  	ver, pushes, err := encode.DecodeBlob(b)
   797  	if err != nil {
   798  		return nil, err
   799  	}
   800  	switch ver {
   801  	case 0:
   802  		return decodeAssetBalance_v0(pushes)
   803  	}
   804  	return nil, fmt.Errorf("unknown Balance version %d", ver)
   805  }
   806  
   807  func decodeAssetBalance_v0(pushes [][]byte) (*asset.Balance, error) {
   808  	if len(pushes) != 3 {
   809  		return nil, fmt.Errorf("decodeBalance_v0: expected 3 push, got %d", len(pushes))
   810  	}
   811  	return &asset.Balance{
   812  		Available: intCoder.Uint64(pushes[0]),
   813  		Immature:  intCoder.Uint64(pushes[1]),
   814  		Locked:    intCoder.Uint64(pushes[2]),
   815  	}, nil
   816  }
   817  
   818  // Balance represents a wallet's balance in various contexts.
   819  type Balance struct {
   820  	asset.Balance
   821  	Stamp time.Time `json:"stamp"`
   822  }
   823  
   824  // Encode encodes the Balance to a versioned blob.
   825  func (b *Balance) Encode() []byte {
   826  	return versionedBytes(0).
   827  		AddData(encodeAssetBalance(&b.Balance)).
   828  		AddData(uint64Bytes(uint64(b.Stamp.UnixMilli())))
   829  }
   830  
   831  // DecodeBalance decodes the versioned blob to a *Balance.
   832  func DecodeBalance(b []byte) (*Balance, error) {
   833  	ver, pushes, err := encode.DecodeBlob(b)
   834  	if err != nil {
   835  		return nil, err
   836  	}
   837  	switch ver {
   838  	case 0:
   839  		return decodeBalance_v0(pushes)
   840  	}
   841  	return nil, fmt.Errorf("unknown Balance version %d", ver)
   842  }
   843  
   844  func decodeBalance_v0(pushes [][]byte) (*Balance, error) {
   845  	if len(pushes) < 2 {
   846  		return nil, fmt.Errorf("decodeBalances_v0: expected >= 2 pushes. got %d", len(pushes))
   847  	}
   848  	if len(pushes)%2 != 0 {
   849  		return nil, fmt.Errorf("decodeBalances_v0: expected an even number of pushes, got %d", len(pushes))
   850  	}
   851  	bal, err := decodeAssetBalance(pushes[0])
   852  	if err != nil {
   853  		return nil, fmt.Errorf("decodeBalances_v0: error decoding zero conf balance: %w", err)
   854  	}
   855  
   856  	return &Balance{
   857  		Balance: *bal,
   858  		Stamp:   time.UnixMilli(int64(intCoder.Uint64(pushes[1]))),
   859  	}, nil
   860  }
   861  
   862  // Wallet is information necessary to create an asset.Wallet.
   863  type Wallet struct {
   864  	AssetID     uint32
   865  	Type        string
   866  	Settings    map[string]string
   867  	Balance     *Balance
   868  	EncryptedPW []byte
   869  	Address     string
   870  	Disabled    bool
   871  }
   872  
   873  // Encode encodes the Wallet to a versioned blob.
   874  func (w *Wallet) Encode() []byte {
   875  	return versionedBytes(1).
   876  		AddData(uint32Bytes(w.AssetID)).
   877  		AddData(config.Data(w.Settings)).
   878  		AddData(w.EncryptedPW).
   879  		AddData([]byte(w.Address)).
   880  		AddData([]byte(w.Type))
   881  }
   882  
   883  // DecodeWallet decodes the versioned blob to a *Wallet. The Balance is NOT set;
   884  // the caller must retrieve it. See for example makeWallet and DecodeBalance.
   885  func DecodeWallet(b []byte) (*Wallet, error) {
   886  	ver, pushes, err := encode.DecodeBlob(b)
   887  	if err != nil {
   888  		return nil, err
   889  	}
   890  	switch ver {
   891  	case 0:
   892  		return decodeWallet_v0(pushes)
   893  	case 1:
   894  		return decodeWallet_v1(pushes)
   895  	}
   896  	return nil, fmt.Errorf("unknown DecodeWallet version %d", ver)
   897  }
   898  
   899  func decodeWallet_v0(pushes [][]byte) (*Wallet, error) {
   900  	// Add a push for wallet type.
   901  	pushes = append(pushes, []byte(""))
   902  	return decodeWallet_v1(pushes)
   903  }
   904  
   905  func decodeWallet_v1(pushes [][]byte) (*Wallet, error) {
   906  	if len(pushes) != 5 {
   907  		return nil, fmt.Errorf("decodeWallet_v1: expected 5 pushes, got %d", len(pushes))
   908  	}
   909  	idB, settingsB, keyB := pushes[0], pushes[1], pushes[2]
   910  	addressB, typeB := pushes[3], pushes[4]
   911  	settings, err := config.Parse(settingsB)
   912  	if err != nil {
   913  		return nil, fmt.Errorf("unable to decode wallet settings")
   914  	}
   915  	return &Wallet{
   916  		AssetID:     intCoder.Uint32(idB),
   917  		Type:        string(typeB),
   918  		Settings:    settings,
   919  		EncryptedPW: keyB,
   920  		Address:     string(addressB),
   921  	}, nil
   922  }
   923  
   924  // ID is the byte-encoded asset ID for this wallet.
   925  func (w *Wallet) ID() []byte {
   926  	return uint32Bytes(w.AssetID)
   927  }
   928  
   929  // SID is a string representation of the wallet's asset ID.
   930  func (w *Wallet) SID() string {
   931  	return strconv.Itoa(int(w.AssetID))
   932  }
   933  
   934  func versionedBytes(v byte) encode.BuildyBytes {
   935  	return encode.BuildyBytes{v}
   936  }
   937  
   938  var uint64Bytes = encode.Uint64Bytes
   939  var uint32Bytes = encode.Uint32Bytes
   940  var uint16Bytes = encode.Uint16Bytes
   941  var intCoder = encode.IntCoder
   942  
   943  // AccountBackup represents a user account backup.
   944  type AccountBackup struct {
   945  	KeyParams []byte
   946  	Accounts  []*AccountInfo
   947  }
   948  
   949  // encodeDEXAccount serializes the details needed to backup a dex account.
   950  func encodeDEXAccount(acct *AccountInfo) []byte {
   951  	return versionedBytes(1).
   952  		AddData([]byte(acct.Host)).
   953  		AddData(acct.LegacyEncKey).
   954  		AddData(acct.DEXPubKey.SerializeCompressed()).
   955  		AddData(acct.EncKeyV2)
   956  }
   957  
   958  // decodeDEXAccount decodes the versioned blob into an AccountInfo.
   959  func decodeDEXAccount(acctB []byte) (*AccountInfo, error) {
   960  	ver, pushes, err := encode.DecodeBlob(acctB)
   961  	if err != nil {
   962  		return nil, err
   963  	}
   964  
   965  	switch ver {
   966  	case 0:
   967  		pushes = append(pushes, nil)
   968  		fallthrough
   969  	case 1:
   970  		if len(pushes) != 4 {
   971  			return nil, fmt.Errorf("expected 4 pushes, got %d", len(pushes))
   972  		}
   973  
   974  		var ai AccountInfo
   975  		ai.Host = string(pushes[0])
   976  		ai.LegacyEncKey = pushes[1]
   977  		ai.DEXPubKey, err = secp256k1.ParsePubKey(pushes[2])
   978  		ai.EncKeyV2 = pushes[3]
   979  		if err != nil {
   980  			return nil, err
   981  		}
   982  		return &ai, nil
   983  
   984  	}
   985  	return nil, fmt.Errorf("unknown DEX account version %d", ver)
   986  }
   987  
   988  // Serialize encodes an account backup as bytes.
   989  func (ab *AccountBackup) Serialize() []byte {
   990  	backup := versionedBytes(0).AddData(ab.KeyParams)
   991  	for _, acct := range ab.Accounts {
   992  		backup = backup.AddData(encodeDEXAccount(acct))
   993  	}
   994  	return backup
   995  }
   996  
   997  // decodeAccountBackup decodes the versioned blob into an *AccountBackup.
   998  func decodeAccountBackup(b []byte) (*AccountBackup, error) {
   999  	ver, pushes, err := encode.DecodeBlob(b)
  1000  	if err != nil {
  1001  		return nil, err
  1002  	}
  1003  	switch ver {
  1004  	case 0, 1:
  1005  		keyParams := pushes[0]
  1006  		accts := make([]*AccountInfo, 0, len(pushes)-1)
  1007  		for _, push := range pushes[1:] {
  1008  			ai, err := decodeDEXAccount(push)
  1009  			if err != nil {
  1010  				return nil, err
  1011  			}
  1012  			accts = append(accts, ai)
  1013  		}
  1014  
  1015  		return &AccountBackup{
  1016  			KeyParams: keyParams,
  1017  			Accounts:  accts,
  1018  		}, nil
  1019  	}
  1020  	return nil, fmt.Errorf("unknown AccountBackup version %d", ver)
  1021  }
  1022  
  1023  // Save persists an account backup to file.
  1024  func (ab *AccountBackup) Save(path string) error {
  1025  	backup := ab.Serialize()
  1026  	return os.WriteFile(path, backup, 0o600)
  1027  }
  1028  
  1029  // RestoreAccountBackup generates a user account from a backup file.
  1030  func RestoreAccountBackup(path string) (*AccountBackup, error) {
  1031  	backup, err := os.ReadFile(path)
  1032  	if err != nil {
  1033  		return nil, err
  1034  	}
  1035  	ab, err := decodeAccountBackup(backup)
  1036  	if err != nil {
  1037  		return nil, err
  1038  	}
  1039  	return ab, nil
  1040  }
  1041  
  1042  // Topic is a language-independent unique ID for a Notification.
  1043  type Topic string
  1044  
  1045  // Notification is information for the user that is typically meant for display,
  1046  // and is persisted for recall across sessions.
  1047  type Notification struct {
  1048  	NoteType    string    `json:"type"`
  1049  	TopicID     Topic     `json:"topic"`
  1050  	SubjectText string    `json:"subject"`
  1051  	DetailText  string    `json:"details"`
  1052  	Severeness  Severity  `json:"severity"`
  1053  	TimeStamp   uint64    `json:"stamp"`
  1054  	Ack         bool      `json:"acked"`
  1055  	Id          dex.Bytes `json:"id"`
  1056  }
  1057  
  1058  // NewNotification is a constructor for a Notification.
  1059  func NewNotification(noteType string, topic Topic, subject, details string, severity Severity) Notification {
  1060  	note := Notification{
  1061  		NoteType:    noteType,
  1062  		TopicID:     topic,
  1063  		SubjectText: subject,
  1064  		DetailText:  details,
  1065  		Severeness:  severity,
  1066  	}
  1067  	note.Stamp()
  1068  	return note
  1069  }
  1070  
  1071  // ID is a unique ID based on a hash of the notification data.
  1072  func (n *Notification) ID() dex.Bytes {
  1073  	return noteKey(n.Encode())
  1074  }
  1075  
  1076  // Type is the notification type.
  1077  func (n *Notification) Type() string {
  1078  	return n.NoteType
  1079  }
  1080  
  1081  // Topic is a language-independent unique ID for the Notification.
  1082  func (n *Notification) Topic() Topic {
  1083  	return n.TopicID
  1084  }
  1085  
  1086  // Subject is a short description of the notification contents.
  1087  func (n *Notification) Subject() string {
  1088  	return n.SubjectText
  1089  }
  1090  
  1091  // Details should contain more detailed information.
  1092  func (n *Notification) Details() string {
  1093  	return n.DetailText
  1094  }
  1095  
  1096  // Severity is the notification severity.
  1097  func (n *Notification) Severity() Severity {
  1098  	return n.Severeness
  1099  }
  1100  
  1101  // Time is the notification timestamp. The timestamp is set in NewNotification.
  1102  func (n *Notification) Time() uint64 {
  1103  	return n.TimeStamp
  1104  }
  1105  
  1106  // Acked is true if the user has seen the notification. Acknowledgement is
  1107  // recorded with DB.AckNotification.
  1108  func (n *Notification) Acked() bool {
  1109  	return n.Ack
  1110  }
  1111  
  1112  // Stamp sets the notification timestamp. If NewNotification is used to
  1113  // construct the Notification, the timestamp will already be set.
  1114  func (n *Notification) Stamp() {
  1115  	n.TimeStamp = uint64(time.Now().UnixMilli())
  1116  	n.Id = n.ID()
  1117  }
  1118  
  1119  // DBNote is a function to return the *Notification itself. It  should really be
  1120  // defined on the concrete types in core, but is ubiquitous so defined here for
  1121  // convenience.
  1122  func (n *Notification) DBNote() *Notification {
  1123  	return n
  1124  }
  1125  
  1126  // String generates a compact human-readable representation of the Notification
  1127  // that is suitable for logging. For example:
  1128  //
  1129  //	|SUCCESS| (fee payment) Fee paid - Waiting for 2 confirmations before trading at https://superdex.tld:7232
  1130  //	|DATA| (boring event) Subject without details
  1131  func (n *Notification) String() string {
  1132  	// In case type and/or detail or empty strings, adjust the formatting to
  1133  	// avoid extra whitespace.
  1134  	var format strings.Builder
  1135  	format.WriteString("|%s| (%s)") // always nil error
  1136  	if len(n.DetailText) > 0 || len(n.SubjectText) > 0 {
  1137  		format.WriteString(" ")
  1138  	}
  1139  	format.WriteString("%s")
  1140  	if len(n.DetailText) > 0 && len(n.SubjectText) > 0 {
  1141  		format.WriteString(" - ")
  1142  	}
  1143  	format.WriteString("%s")
  1144  
  1145  	severity := strings.ToUpper(n.Severity().String())
  1146  	return fmt.Sprintf(format.String(), severity, n.NoteType, n.SubjectText, n.DetailText)
  1147  }
  1148  
  1149  // DecodeNotification decodes the versioned blob to a *Notification.
  1150  func DecodeNotification(b []byte) (*Notification, error) {
  1151  	ver, pushes, err := encode.DecodeBlob(b)
  1152  	if err != nil {
  1153  		return nil, err
  1154  	}
  1155  	switch ver {
  1156  	case 1:
  1157  		return decodeNotification_v1(pushes)
  1158  	case 0:
  1159  		return decodeNotification_v0(pushes)
  1160  	}
  1161  	return nil, fmt.Errorf("unknown DecodeNotification version %d", ver)
  1162  }
  1163  
  1164  func decodeNotification_v0(pushes [][]byte) (*Notification, error) {
  1165  	return decodeNotification_v1(append(pushes, []byte{}))
  1166  }
  1167  
  1168  func decodeNotification_v1(pushes [][]byte) (*Notification, error) {
  1169  	if len(pushes) != 6 {
  1170  		return nil, fmt.Errorf("decodeNotification_v0: expected 5 pushes, got %d", len(pushes))
  1171  	}
  1172  	if len(pushes[3]) != 1 {
  1173  		return nil, fmt.Errorf("decodeNotification_v0: severity push is supposed to be length 1. got %d", len(pushes[2]))
  1174  	}
  1175  
  1176  	return &Notification{
  1177  		NoteType:    string(pushes[0]),
  1178  		TopicID:     Topic(string(pushes[5])),
  1179  		SubjectText: string(pushes[1]),
  1180  		DetailText:  string(pushes[2]),
  1181  		Severeness:  Severity(pushes[3][0]),
  1182  		TimeStamp:   intCoder.Uint64(pushes[4]),
  1183  	}, nil
  1184  }
  1185  
  1186  // Encode encodes the Notification to a versioned blob.
  1187  func (n *Notification) Encode() []byte {
  1188  	return versionedBytes(1).
  1189  		AddData([]byte(n.NoteType)).
  1190  		AddData([]byte(n.SubjectText)).
  1191  		AddData([]byte(n.DetailText)).
  1192  		AddData([]byte{byte(n.Severeness)}).
  1193  		AddData(uint64Bytes(n.TimeStamp)).
  1194  		AddData([]byte(n.TopicID))
  1195  }
  1196  
  1197  type OrderFilterMarket struct {
  1198  	Base  uint32
  1199  	Quote uint32
  1200  }
  1201  
  1202  // OrderFilter is used to limit the results returned by a query to (DB).Orders.
  1203  type OrderFilter struct {
  1204  	// N is the number of orders to return in the set.
  1205  	N int
  1206  	// Offset can be used to shift the window of the time-sorted orders such
  1207  	// that any orders that would sort to index <= the order specified by Offset
  1208  	// will be rejected.
  1209  	Offset order.OrderID
  1210  	// Hosts is a list of acceptable hosts. A zero-length Hosts means all
  1211  	// hosts are accepted.
  1212  	Hosts []string
  1213  	// Assets is a list of BIP IDs for acceptable assets. A zero-length Assets
  1214  	// means all assets are accepted.
  1215  	Assets []uint32
  1216  	// Market limits results to a specific market.
  1217  	Market *OrderFilterMarket
  1218  	// Statuses is a list of acceptable statuses. A zero-length Statuses means
  1219  	// all statuses are accepted.
  1220  	Statuses []order.OrderStatus
  1221  }
  1222  
  1223  // noteKeySize must be <= 32.
  1224  const noteKeySize = 8
  1225  
  1226  // noteKey creates a unique key from the hash of the supplied bytes.
  1227  func noteKey(b []byte) []byte {
  1228  	h := blake2s.Sum256(b)
  1229  	return h[:noteKeySize]
  1230  }
  1231  
  1232  // hashKey creates a unique key from the hash of the supplied bytes.
  1233  func hashKey(b []byte) []byte {
  1234  	h := blake2s.Sum256(b)
  1235  	return h[:]
  1236  }