Skip to content

Commit fb68f36

Browse files
authored
Merge pull request #10048 from ziggie1984/fix-utxonursery-encoding
contractcourt: fix encoding
2 parents d40ac45 + 9bff228 commit fb68f36

File tree

3 files changed

+296
-9
lines changed

3 files changed

+296
-9
lines changed

contractcourt/utxonursery.go

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,44 +1514,160 @@ func (k *kidOutput) Encode(w io.Writer) error {
15141514
// Decode takes a byte array representation of a kidOutput and converts it to an
15151515
// struct. Note that the witnessFunc method isn't added during deserialization
15161516
// and must be added later based on the value of the witnessType field.
1517+
//
1518+
// NOTE: We need to support both formats because we did not migrate the database
1519+
// to the new format so the support for the legacy format is still needed.
15171520
func (k *kidOutput) Decode(r io.Reader) error {
1521+
// Read all available data into a buffer first so we can try both
1522+
// formats.
1523+
//
1524+
// NOTE: We can consume the whole reader here because every kidOutput is
1525+
// saved separately via a key-value pair and we are only decoding them
1526+
// individually so there is no risk of reading multiple kidOutputs.
1527+
var buf bytes.Buffer
1528+
_, err := io.Copy(&buf, r)
1529+
if err != nil {
1530+
return err
1531+
}
1532+
1533+
data := buf.Bytes()
1534+
bufReader := bytes.NewReader(data)
1535+
1536+
// Try the new format first. A successful decode must consume all bytes.
1537+
newErr := k.decodeNewFormat(bufReader)
1538+
if newErr == nil && bufReader.Len() == 0 {
1539+
return nil
1540+
}
1541+
1542+
// If that fails, reset the reader and try the legacy format.
1543+
_, err = bufReader.Seek(0, io.SeekStart)
1544+
if err != nil {
1545+
return err
1546+
}
1547+
1548+
legacyErr := k.decodeLegacyFormat(bufReader)
1549+
if legacyErr != nil {
1550+
return fmt.Errorf("failed to decode with both new and "+
1551+
"legacy formats: new=%v, legacy=%v", newErr, legacyErr)
1552+
}
1553+
1554+
// The legacy format must also consume all bytes.
1555+
if bufReader.Len() > 0 {
1556+
return fmt.Errorf("legacy decode has %d trailing bytes",
1557+
bufReader.Len())
1558+
}
1559+
1560+
return nil
1561+
}
1562+
1563+
// decodeNewFormat decodes using the new format with variable-length outpoint
1564+
// encoding.
1565+
func (k *kidOutput) decodeNewFormat(r *bytes.Reader) error {
15181566
var scratch [8]byte
15191567

1520-
if _, err := r.Read(scratch[:]); err != nil {
1568+
if _, err := io.ReadFull(r, scratch[:]); err != nil {
15211569
return err
15221570
}
15231571
k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
15241572

1525-
err := graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.outpoint)
1526-
if err != nil {
1573+
// The outpoint does use the new format without a preceding varint.
1574+
if err := graphdb.ReadOutpoint(r, &k.outpoint); err != nil {
15271575
return err
15281576
}
15291577

1530-
err = graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
1531-
if err != nil {
1578+
// The origin chan point does use the new format without a preceding
1579+
// varint..
1580+
if err := graphdb.ReadOutpoint(r, &k.originChanPoint); err != nil {
1581+
return err
1582+
}
1583+
1584+
if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
1585+
return err
1586+
}
1587+
1588+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1589+
return err
1590+
}
1591+
k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
1592+
1593+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1594+
return err
1595+
}
1596+
k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
1597+
1598+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1599+
return err
1600+
}
1601+
k.confHeight = byteOrder.Uint32(scratch[:4])
1602+
1603+
if _, err := io.ReadFull(r, scratch[:2]); err != nil {
1604+
return err
1605+
}
1606+
k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
1607+
1608+
if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
1609+
return err
1610+
}
1611+
1612+
// If there's anything left in the reader, then this is a taproot
1613+
// output that also wrote a control block.
1614+
ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
1615+
switch {
1616+
// If there're no bytes remaining, then we'll return early.
1617+
case errors.Is(err, io.EOF):
1618+
fallthrough
1619+
case errors.Is(err, io.ErrUnexpectedEOF):
1620+
return nil
1621+
1622+
case err != nil:
1623+
return err
1624+
}
1625+
1626+
k.signDesc.ControlBlock = ctrlBlock
1627+
1628+
return nil
1629+
}
1630+
1631+
// decodeLegacyFormat decodes using the legacy format with fixed-length outpoint
1632+
// encoding.
1633+
func (k *kidOutput) decodeLegacyFormat(r *bytes.Reader) error {
1634+
var scratch [8]byte
1635+
1636+
if _, err := io.ReadFull(r, scratch[:]); err != nil {
1637+
return err
1638+
}
1639+
k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
1640+
1641+
// Outpoint uses the legacy format with a preceding varint.
1642+
if err := readOutpointVarBytes(r, &k.outpoint); err != nil {
1643+
return err
1644+
}
1645+
1646+
// Origin chan point uses the legacy format with a preceding varint.
1647+
if err := readOutpointVarBytes(r, &k.originChanPoint); err != nil {
15321648
return err
15331649
}
15341650

15351651
if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
15361652
return err
15371653
}
15381654

1539-
if _, err := r.Read(scratch[:4]); err != nil {
1655+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
15401656
return err
15411657
}
15421658
k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
15431659

1544-
if _, err := r.Read(scratch[:4]); err != nil {
1660+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
15451661
return err
15461662
}
15471663
k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
15481664

1549-
if _, err := r.Read(scratch[:4]); err != nil {
1665+
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
15501666
return err
15511667
}
15521668
k.confHeight = byteOrder.Uint32(scratch[:4])
15531669

1554-
if _, err := r.Read(scratch[:2]); err != nil {
1670+
if _, err := io.ReadFull(r, scratch[:2]); err != nil {
15551671
return err
15561672
}
15571673
k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
@@ -1579,6 +1695,24 @@ func (k *kidOutput) Decode(r io.Reader) error {
15791695
return nil
15801696
}
15811697

1698+
// readOutpointVarBytes reads an outpoint using the variable-length encoding.
1699+
func readOutpointVarBytes(r io.Reader, o *wire.OutPoint) error {
1700+
scratch := make([]byte, 4)
1701+
1702+
txid, err := wire.ReadVarBytes(r, 0, 32, "prevout")
1703+
if err != nil {
1704+
return err
1705+
}
1706+
copy(o.Hash[:], txid)
1707+
1708+
if _, err := r.Read(scratch); err != nil {
1709+
return err
1710+
}
1711+
o.Index = byteOrder.Uint32(scratch)
1712+
1713+
return nil
1714+
}
1715+
15821716
// Compile-time constraint to ensure kidOutput implements the
15831717
// Input interface.
15841718

contractcourt/utxonursery_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package contractcourt
22

33
import (
44
"bytes"
5+
"encoding/binary"
56
"fmt"
7+
"io"
68
"math"
79
"os"
810
"reflect"
@@ -1113,3 +1115,150 @@ func (s *mockSweeperFull) sweepAll() {
11131115
}
11141116
}
11151117
}
1118+
1119+
// writeOutpointVarBytes writes an outpoint using the variable-length encoding.
1120+
func writeOutpointVarBytes(w io.Writer, o *wire.OutPoint) error {
1121+
if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil {
1122+
return err
1123+
}
1124+
1125+
var scratch [4]byte
1126+
byteOrder.PutUint32(scratch[:], o.Index)
1127+
_, err := w.Write(scratch[:])
1128+
1129+
return err
1130+
}
1131+
1132+
// encodeKidOutputLegacy encodes a kidOutput using the legacy format.
1133+
func encodeKidOutputLegacy(w io.Writer, k *kidOutput) error {
1134+
var scratch [8]byte
1135+
byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
1136+
if _, err := w.Write(scratch[:]); err != nil {
1137+
return err
1138+
}
1139+
1140+
op := k.OutPoint()
1141+
if err := writeOutpointVarBytes(w, &op); err != nil {
1142+
return err
1143+
}
1144+
if err := writeOutpointVarBytes(w, k.OriginChanPoint()); err != nil {
1145+
return err
1146+
}
1147+
1148+
if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
1149+
return err
1150+
}
1151+
1152+
byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
1153+
if _, err := w.Write(scratch[:4]); err != nil {
1154+
return err
1155+
}
1156+
1157+
byteOrder.PutUint32(scratch[:4], k.absoluteMaturity)
1158+
if _, err := w.Write(scratch[:4]); err != nil {
1159+
return err
1160+
}
1161+
1162+
byteOrder.PutUint32(scratch[:4], k.ConfHeight())
1163+
if _, err := w.Write(scratch[:4]); err != nil {
1164+
return err
1165+
}
1166+
1167+
byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
1168+
if _, err := w.Write(scratch[:2]); err != nil {
1169+
return err
1170+
}
1171+
1172+
if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
1173+
return err
1174+
}
1175+
1176+
if k.SignDesc().ControlBlock == nil {
1177+
return nil
1178+
}
1179+
1180+
return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
1181+
}
1182+
1183+
// TestKidOutputDecode tests that we can decode a kidOutput from both the
1184+
// new and legacy formats. It also checks that the decoded output matches the
1185+
// original output, except for the deadlineHeight field, which is not encoded
1186+
// in the legacy format.
1187+
func TestKidOutputDecode(t *testing.T) {
1188+
t.Parallel()
1189+
1190+
op := wire.OutPoint{
1191+
Hash: chainhash.Hash{1},
1192+
Index: 1,
1193+
}
1194+
originOp := wire.OutPoint{
1195+
Hash: chainhash.Hash{2},
1196+
Index: 2,
1197+
}
1198+
pkScript := []byte{
1199+
0x00, 0x14, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
1200+
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
1201+
0x13, 0x14,
1202+
}
1203+
signDesc := &input.SignDescriptor{
1204+
Output: &wire.TxOut{
1205+
Value: 12345,
1206+
PkScript: pkScript,
1207+
},
1208+
HashType: txscript.SigHashAll,
1209+
WitnessScript: []byte{},
1210+
}
1211+
1212+
// Since makeKidOutput is not exported, we construct the kid output
1213+
// manually.
1214+
kid := kidOutput{
1215+
breachedOutput: breachedOutput{
1216+
amt: btcutil.Amount(signDesc.Output.Value),
1217+
outpoint: op,
1218+
witnessType: input.CommitmentRevoke,
1219+
signDesc: *signDesc,
1220+
confHeight: 100,
1221+
},
1222+
originChanPoint: originOp,
1223+
blocksToMaturity: 144,
1224+
isHtlc: false,
1225+
absoluteMaturity: 0,
1226+
}
1227+
1228+
// Encode the kid output in both formats.
1229+
var newBuf bytes.Buffer
1230+
err := kid.Encode(&newBuf)
1231+
require.NoError(t, err)
1232+
1233+
var legacyBuf bytes.Buffer
1234+
err = encodeKidOutputLegacy(&legacyBuf, &kid)
1235+
require.NoError(t, err)
1236+
1237+
testCases := []struct {
1238+
name string
1239+
data []byte
1240+
}{
1241+
{
1242+
name: "new format",
1243+
data: newBuf.Bytes(),
1244+
},
1245+
{
1246+
name: "legacy format",
1247+
data: legacyBuf.Bytes(),
1248+
},
1249+
}
1250+
1251+
for _, tc := range testCases {
1252+
t.Run(tc.name, func(t *testing.T) {
1253+
var decodedKid kidOutput
1254+
err := decodedKid.Decode(bytes.NewReader(tc.data))
1255+
require.NoError(t, err)
1256+
1257+
// The deadlineHeight field is not encoded, so we need
1258+
// to set it manually for the comparison.
1259+
kid.deadlineHeight = decodedKid.deadlineHeight
1260+
1261+
require.Equal(t, kid, decodedKid)
1262+
})
1263+
}
1264+
}

docs/release-notes/release-notes-0.19.2.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
- Fixed a [case](https://github.com/lightningnetwork/lnd/pull/10045) that a
4343
panic may happen which prevents the node from starting up.
4444

45+
- Fixed a [case](https://github.com/lightningnetwork/lnd/pull/10048) where we
46+
would not be able to decode persisted data in the utxo nursery and therefore
47+
would fail to start up.
48+
4549
# New Features
4650

4751
## Functional Enhancements

0 commit comments

Comments
 (0)