mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
account+clientdb: add version to account
This commit is contained in:
parent
1ef87fe37b
commit
7bc48a7a7d
7 changed files with 186 additions and 4 deletions
|
|
@ -41,6 +41,20 @@ type Reservation struct {
|
|||
InitialBatchKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// Version represents the version of an account.
|
||||
type Version uint8
|
||||
|
||||
const (
|
||||
// VersionInitialNoVersion is the initial version any legacy account has
|
||||
// that technically wasn't versioned at all. The version field isn't
|
||||
// even serialized for those accounts.
|
||||
VersionInitialNoVersion Version = 0
|
||||
|
||||
// VersionTaprootEnabled is the version that introduced account
|
||||
// versioning and the upgrade to Taproot (with MuSig2 multi-sig).
|
||||
VersionTaprootEnabled Version = 1
|
||||
)
|
||||
|
||||
// State describes the different possible states of an account.
|
||||
type State uint8
|
||||
|
||||
|
|
@ -192,6 +206,9 @@ type Account struct {
|
|||
// NOTE: This is only nil within the StateInitiated phase. There are no
|
||||
// guarantees as to whether the transaction has its witness populated.
|
||||
LatestTx *wire.MsgTx
|
||||
|
||||
// Version is the version of the account.
|
||||
Version Version
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -250,6 +267,7 @@ func (a *Account) Copy(modifiers ...Modifier) *Account {
|
|||
State: a.State,
|
||||
HeightHint: a.HeightHint,
|
||||
OutPoint: a.OutPoint,
|
||||
Version: a.Version,
|
||||
}
|
||||
if a.State != StateInitiated {
|
||||
accountCopy.LatestTx = a.LatestTx.Copy()
|
||||
|
|
@ -318,6 +336,14 @@ func LatestTxModifier(tx *wire.MsgTx) Modifier {
|
|||
}
|
||||
}
|
||||
|
||||
// VersionModifier is a functional option that modifies the version of an
|
||||
// account.
|
||||
func VersionModifier(version Version) Modifier {
|
||||
return func(account *Account) {
|
||||
account.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
// Store is responsible for storing and retrieving account information reliably.
|
||||
type Store interface {
|
||||
// AddAccount adds a record for the account to the database.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ func TestAccountCopy(t *testing.T) {
|
|||
HeightHint: 1,
|
||||
OutPoint: wire.OutPoint{Index: 1},
|
||||
LatestTx: wire.NewMsgTx(2),
|
||||
Version: VersionTaprootEnabled,
|
||||
}
|
||||
|
||||
a.Value = 2
|
||||
|
|
|
|||
|
|
@ -7,9 +7,22 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightninglabs/pool/account"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// accountStateVersionedMask is a bit mask for detecting from the state
|
||||
// of an account whether that account has a version field encoded with
|
||||
// it or not. We use the first bit of the uint8 state field because we
|
||||
// are unlikely to ever have more than 127 different states.
|
||||
accountStateVersionedMask account.State = 0b1000_0000
|
||||
|
||||
// accountVersionType is the first additional field we added to the
|
||||
// account as a TLV field and it encodes the account's version.
|
||||
accountVersionType tlv.Type = 0
|
||||
)
|
||||
|
||||
var (
|
||||
// accountBucketKey is the top level bucket where we can find all
|
||||
// information about complete accounts. These accounts are indexed by
|
||||
|
|
@ -21,6 +34,23 @@ var (
|
|||
ErrAccountNotFound = errors.New("account not found")
|
||||
)
|
||||
|
||||
// isVersioned returns true if the version bit is set in the given account
|
||||
// state.
|
||||
func isVersioned(state account.State) bool {
|
||||
return state&accountStateVersionedMask == accountStateVersionedMask
|
||||
}
|
||||
|
||||
// setVersionBit sets the version bit in the given account state.
|
||||
func setVersionBit(state account.State) account.State {
|
||||
return state | accountStateVersionedMask
|
||||
}
|
||||
|
||||
// clearVersionBit clears the version bit in the given account state.
|
||||
func clearVersionBit(state account.State) account.State {
|
||||
// The &^ operator means AND NOT, also known as the Bitclear operator.
|
||||
return state &^ accountStateVersionedMask
|
||||
}
|
||||
|
||||
// getAccountKey returns the key for an account which is not partial.
|
||||
func getAccountKey(account *account.Account) []byte {
|
||||
return account.TraderKey.PubKey.SerializeCompressed()
|
||||
|
|
@ -156,9 +186,15 @@ func readAccount(sourceBucket *bbolt.Bucket,
|
|||
}
|
||||
|
||||
func serializeAccount(w *bytes.Buffer, a *account.Account) error {
|
||||
rawState := a.State
|
||||
accountIsVersioned := a.Version > account.VersionInitialNoVersion
|
||||
if accountIsVersioned {
|
||||
rawState = setVersionBit(rawState)
|
||||
}
|
||||
|
||||
err := WriteElements(
|
||||
w, a.Value, a.Expiry, a.TraderKey, a.AuctioneerKey, a.BatchKey,
|
||||
a.Secret, a.State, a.HeightHint, a.OutPoint,
|
||||
a.Secret, rawState, a.HeightHint, a.OutPoint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -175,19 +211,107 @@ func serializeAccount(w *bytes.Buffer, a *account.Account) error {
|
|||
}
|
||||
}
|
||||
|
||||
// The version flag encoded within the state will inform the
|
||||
// de-serialize method that it should read another field. Therefore, we
|
||||
// can safely write it here.
|
||||
if accountIsVersioned {
|
||||
if err := serializeAccountTlvData(w, a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serializeAccountTlvData writes all additional TLV fields of an account to the
|
||||
// given writer. This should only be called for accounts with a version > 0 as
|
||||
// otherwise this will mess up the assumptions used for encoding/decoding
|
||||
// accounts within a batch snapshot blob.
|
||||
func serializeAccountTlvData(w *bytes.Buffer, a *account.Account) error {
|
||||
version := uint8(a.Version)
|
||||
tlvRecords := []tlv.Record{
|
||||
tlv.MakePrimitiveRecord(accountVersionType, &version),
|
||||
}
|
||||
|
||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We can't just encode the stream to the writer directly, because there
|
||||
// might be multiple accounts lined up after each other. And since a TLV
|
||||
// reader will always try to read until the end of a stream, we need to
|
||||
// be able to cap it somehow. So we write the number of bytes and then
|
||||
// the stream bytes itself.
|
||||
var buf bytes.Buffer
|
||||
err = tlvStream.Encode(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteElements(w, uint32(buf.Len()), buf.Bytes())
|
||||
}
|
||||
|
||||
// deserializeAccountTlvData reads all additional TLV fields of an account from
|
||||
// the given reader. This should only be called for accounts with a version > 0
|
||||
// as otherwise this will mess up the assumptions used for encoding/decoding
|
||||
// accounts within a batch snapshot blob.
|
||||
func deserializeAccountTlvData(r io.Reader, a *account.Account) error {
|
||||
// We first need to find out how many bytes there are for this TLV
|
||||
// stream and only read those bytes. Otherwise, the TLV reader will try
|
||||
// to read as many bytes as it can.
|
||||
var streamLen uint32
|
||||
if err := ReadElement(r, &streamLen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
streamBytes := make([]byte, streamLen)
|
||||
if err := ReadElement(r, streamBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
version uint8
|
||||
)
|
||||
tlvStream, err := tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(accountVersionType, &version),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsedTypes, err := tlvStream.DecodeWithParsedTypes(
|
||||
bytes.NewReader(streamBytes),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := parsedTypes[accountVersionType]; ok && t == nil {
|
||||
a.Version = account.Version(version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deserializeAccount(r io.Reader) (*account.Account, error) {
|
||||
var a account.Account
|
||||
var (
|
||||
a account.Account
|
||||
rawState account.State
|
||||
)
|
||||
err := ReadElements(
|
||||
r, &a.Value, &a.Expiry, &a.TraderKey, &a.AuctioneerKey,
|
||||
&a.BatchKey, &a.Secret, &a.State, &a.HeightHint, &a.OutPoint,
|
||||
&a.BatchKey, &a.Secret, &rawState, &a.HeightHint, &a.OutPoint,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We might have a version flag encoded within the state. We want to
|
||||
// hide that internal mechanism from the caller, so we need to remove
|
||||
// the flag again.
|
||||
a.State = clearVersionBit(rawState)
|
||||
|
||||
// The latest transaction is not found within StateInitiated and
|
||||
// StateCanceledAfterRecovery.
|
||||
switch a.State {
|
||||
|
|
@ -199,5 +323,13 @@ func deserializeAccount(r io.Reader) (*account.Account, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// If there was a version flag, we know we're supposed to read another
|
||||
// field here.
|
||||
if isVersioned(rawState) {
|
||||
if err := deserializeAccountTlvData(r, &a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &a, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ func TestAccounts(t *testing.T) {
|
|||
a, account.StateModifier(account.StatePendingOpen),
|
||||
account.OutPointModifier(accountPoint),
|
||||
account.LatestTxModifier(accountTx),
|
||||
account.VersionModifier(account.VersionTaprootEnabled),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to update account: %v", err)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ type LocalBatchSnapshot struct {
|
|||
MatchedOrders map[order.Nonce][]*order.MatchedOrder
|
||||
}
|
||||
|
||||
// NewSnapshots creates a new LocalBatchSnapshot from the passed order batched.
|
||||
// NewSnapshot creates a new LocalBatchSnapshot from the passed order batched.
|
||||
func NewSnapshot(batch *order.Batch, ourOrders []order.Order,
|
||||
accounts []*account.Account) (*LocalBatchSnapshot, error) {
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,17 @@ var (
|
|||
State: account.StateInitiated,
|
||||
HeightHint: 1,
|
||||
}
|
||||
testAccountTaproot = &account.Account{
|
||||
Value: btcutil.SatoshiPerBitcoin,
|
||||
Expiry: 1337,
|
||||
TraderKey: testTraderKeyDesc,
|
||||
AuctioneerKey: testAuctioneerKey,
|
||||
BatchKey: testBatchKey,
|
||||
Secret: sharedSecret,
|
||||
State: account.StateInitiated,
|
||||
HeightHint: 1,
|
||||
Version: account.VersionTaprootEnabled,
|
||||
}
|
||||
|
||||
testNonce1 = order.Nonce([32]byte{1, 1, 1})
|
||||
testNonce2 = order.Nonce([32]byte{2, 2, 2})
|
||||
|
|
@ -35,6 +46,7 @@ var (
|
|||
|
||||
testAccounts = map[[33]byte]*account.Account{
|
||||
testRawTraderKeyArr: testAccount,
|
||||
{1, 2, 3}: testAccountTaproot,
|
||||
}
|
||||
|
||||
testOrders = map[order.Nonce]order.Order{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ func WriteElement(w *bytes.Buffer, element interface{}) error {
|
|||
case account.State:
|
||||
return lnwire.WriteElement(w, uint8(e))
|
||||
|
||||
case account.Version:
|
||||
return lnwire.WriteElement(w, uint8(e))
|
||||
|
||||
case order.Version:
|
||||
return lnwire.WriteElement(w, uint32(e))
|
||||
|
||||
|
|
@ -152,6 +155,13 @@ func ReadElement(r io.Reader, element interface{}) error { // nolint:gocyclo
|
|||
}
|
||||
*e = account.State(s)
|
||||
|
||||
case *account.Version:
|
||||
var v uint8
|
||||
if err := lnwire.ReadElement(r, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = account.Version(v)
|
||||
|
||||
case *order.Version:
|
||||
var v uint32
|
||||
if err := lnwire.ReadElement(r, &v); err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue