Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ef4954d

Browse files
committed
psbt: add comprehensive PSBTv2 BIP-370 compliance tests
Add complete test coverage for PSBTv2 implementation: - Lifecycle tests (creation, update, finalization, extraction) - Lock time determination algorithm validation - Field validation and serialization round-trips - BIP-370 compliance verification - Unknown field handling tests
1 parent 5fac369 commit ef4954d

2 files changed

Lines changed: 1679 additions & 0 deletions

File tree

btcutil/psbt/psbt_test.go

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,3 +1690,248 @@ func TestUnknowns(t *testing.T) {
16901690
require.NoError(t, err)
16911691
require.Equal(t, packetWithUnknowns, encoded)
16921692
}
1693+
1694+
// TestPsbtV2LifeCycle ensures that the full lifecycle of a PSBTv2 (creating,
1695+
// constructing, serializing, and extracting) works as expected.
1696+
func TestPsbtV2LifeCycle(t *testing.T) {
1697+
1698+
// 1. Create a new V2 PSBT.
1699+
p, err := NewV2(2, 0, 0)
1700+
require.NoError(t, err)
1701+
1702+
// 2. Add an input with a specific sequence (e.g., 0 for RBF).
1703+
txid, _ := chainhash.NewHashFromStr("0102030405060708091011121314151617181920212223242526272829303132")
1704+
1705+
outPoint := wire.NewOutPoint(txid, 1)
1706+
err = p.AddInput(*outPoint, 0)
1707+
require.NoError(t, err)
1708+
1709+
// 3. Add an output.
1710+
script, _ := hex.DecodeString("76a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac")
1711+
err = p.AddOutput(100000000, script)
1712+
require.NoError(t, err)
1713+
1714+
// 4. Serialize and Parse back.
1715+
var b bytes.Buffer
1716+
err = p.Serialize(&b)
1717+
require.NoError(t, err)
1718+
1719+
p2, err := NewFromRawBytes(&b, false)
1720+
require.NoError(t, err)
1721+
1722+
// 5. Verify fields survived the round-trip.
1723+
require.Equal(t, uint32(2), p2.TxVersion)
1724+
require.Equal(t, uint32(1), p2.InputCount)
1725+
require.Equal(t, txid[:], p2.Inputs[0].PreviousTxid)
1726+
require.Equal(t, uint32(0), p2.Inputs[0].Sequence)
1727+
require.Equal(t, int64(100000000), p2.Outputs[0].Amount)
1728+
1729+
// 6. Extract the transaction and verify it works.
1730+
// (Note: For extraction to work, we need to bypass the IsComplete check
1731+
// or finalize it. Since we are testing construction, we can test
1732+
// GetUnsignedTx directly).
1733+
msgTx, err := p2.GetUnsignedTx()
1734+
require.NoError(t, err)
1735+
require.Equal(t, int32(2), msgTx.Version)
1736+
require.Equal(t, uint32(0), msgTx.TxIn[0].Sequence)
1737+
require.Equal(t, int64(100000000), msgTx.TxOut[0].Value)
1738+
}
1739+
1740+
// TestPsbtV2Validation verifies that PSBTv2 packets are validated correctly
1741+
// for strict versioning rules and mandatory field combinations.
1742+
func TestPsbtV2Validation(t *testing.T) {
1743+
t.Run("V2 cannot have global UnsignedTx", func(t *testing.T) {
1744+
// Construct raw bytes with both Version 2 AND UnsignedTx (0x00 forbidden in V2).
1745+
// Magic + Version (0xfb: 2) + UnsignedTx (0x00: minimal 1-byte tx)
1746+
raw := []byte{
1747+
0x70, 0x73, 0x62, 0x74, 0xff, // Magic
1748+
0x01, 0xfb, 0x04, 0x02, 0x00, 0x00, 0x00, // Version 2
1749+
0x01, 0x00, 0x01, 0x01, // UnsignedTx (keyCode 0x00, minimal value)
1750+
0x00, // Separator
1751+
}
1752+
1753+
_, err := NewFromRawBytes(bytes.NewReader(raw), false)
1754+
require.Error(t, err)
1755+
})
1756+
1757+
t.Run("V2 must have InputCount and OutputCount", func(t *testing.T) {
1758+
// Create a raw V2 serialization but manually omit counts.
1759+
// Magic + Version (0xfb: 2) + TxVersion (0x02: 2)
1760+
raw := []byte{
1761+
0x70, 0x73, 0x62, 0x74, 0xff, // Magic
1762+
0x01, 0xfb, 0x04, 0x02, 0x00, 0x00, 0x00, // Version 2
1763+
0x01, 0x02, 0x04, 0x02, 0x00, 0x00, 0x00, // TxVersion 2
1764+
0x00, // Separator
1765+
}
1766+
1767+
// parsing should fail because InputCount/OutputCount are missing for V2.
1768+
_, err := NewFromRawBytes(bytes.NewReader(raw), false)
1769+
require.Error(t, err)
1770+
})
1771+
1772+
t.Run("Unsupported version should fail", func(t *testing.T) {
1773+
// Version 3 (unsupported)
1774+
raw := []byte{
1775+
0x70, 0x73, 0x62, 0x74, 0xff, // Magic
1776+
0x01, 0xfb, 0x04, 0x03, 0x00, 0x00, 0x00, // Version 3
1777+
0x00, // Separator
1778+
}
1779+
1780+
_, err := NewFromRawBytes(bytes.NewReader(raw), false)
1781+
require.Error(t, err)
1782+
})
1783+
}
1784+
1785+
// TestPsbtV2Counts ensures that the number of inputs and outputs parsed
1786+
// matches the InputCount and OutputCount global fields.
1787+
func TestPsbtV2Counts(t *testing.T) {
1788+
// Create a V2 PSBT that claims 2 inputs but only provides 1.
1789+
p, err := NewV2(2, 0, 0)
1790+
require.NoError(t, err)
1791+
1792+
txid, _ := chainhash.NewHashFromStr("0102030405060708091011121314151617181920212223242526272829303132")
1793+
outPoint := wire.NewOutPoint(txid, 1)
1794+
err = p.AddInput(*outPoint, 0xffffffff)
1795+
require.NoError(t, err)
1796+
1797+
script, _ := hex.DecodeString("00")
1798+
err = p.AddOutput(1000, script)
1799+
require.NoError(t, err)
1800+
1801+
// Manually override counts to mismatch reality.
1802+
p.InputCount = 2
1803+
p.OutputCount = 1
1804+
1805+
var b bytes.Buffer
1806+
err = p.Serialize(&b)
1807+
require.NoError(t, err)
1808+
1809+
// Parsing should fail because we promised 2 inputs but only 1 followed.
1810+
_, err = NewFromRawBytes(&b, false)
1811+
require.Error(t, err)
1812+
}
1813+
1814+
// TestPsbtV2Locktimes verifies that PSBTv2 locktime fields are correctly handled.
1815+
func TestPsbtV2Locktimes(t *testing.T) {
1816+
// Create a V2 PSBT with a fallback locktime.
1817+
p, err := NewV2(2, 500000, 0x01) // Fallback 500000, InputsModifiable
1818+
require.NoError(t, err)
1819+
1820+
txid, _ := chainhash.NewHashFromStr("0102030405060708091011121314151617181920212223242526272829303132")
1821+
outPoint := wire.NewOutPoint(txid, 1)
1822+
err = p.AddInput(*outPoint, 0xffffffff)
1823+
require.NoError(t, err)
1824+
1825+
p.Inputs[0].HeightLocktime = 600000
1826+
1827+
msgTx, err := p.GetUnsignedTx()
1828+
require.NoError(t, err)
1829+
1830+
// Since we have a height locktime in an input, it should take precedence.
1831+
require.Equal(t, uint32(600000), msgTx.LockTime)
1832+
1833+
// Now try with time locktime.
1834+
p.Inputs[0].HeightLocktime = 0
1835+
p.Inputs[0].TimeLocktime = 1600000000
1836+
msgTx, err = p.GetUnsignedTx()
1837+
require.NoError(t, err)
1838+
require.Equal(t, uint32(1600000000), msgTx.LockTime)
1839+
1840+
// Test combined locktimes (BIP suggests highest value for same type,
1841+
// but here we just check our extraction logic).
1842+
err = p.AddInput(*outPoint, 0xffffffff)
1843+
require.NoError(t, err)
1844+
p.Inputs[1].TimeLocktime = 1700000000
1845+
1846+
msgTx, err = p.GetUnsignedTx()
1847+
require.NoError(t, err)
1848+
require.Equal(t, uint32(1700000000), msgTx.LockTime)
1849+
1850+
// Test modifiability flag.
1851+
require.Equal(t, uint8(0x01), p.TxModifiable)
1852+
}
1853+
1854+
// TestPSBTv2DetermineLockTimeAlgorithm tests the comprehensive BIP-370 lock time determination algorithm
1855+
func TestPSBTv2DetermineLockTimeAlgorithm(t *testing.T) {
1856+
t.Run("Height-based preference when both supported (BIP-370 tie-breaker)", func(t *testing.T) {
1857+
p, err := NewV2(2, 100000, 0)
1858+
require.NoError(t, err)
1859+
1860+
txid, _ := chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
1861+
1862+
// Input 1: Both time and height (flexible)
1863+
p.AddInput(*wire.NewOutPoint(txid, 0), 0)
1864+
p.Inputs[0].TimeLocktime = 1600000000
1865+
p.Inputs[0].HeightLocktime = 550000
1866+
1867+
// Input 2: Both time and height (flexible)
1868+
p.AddInput(*wire.NewOutPoint(txid, 1), 0)
1869+
p.Inputs[1].TimeLocktime = 1650000000
1870+
p.Inputs[1].HeightLocktime = 600000
1871+
1872+
// BIP-370: "height-based must be chosen" when both supported
1873+
lockTime, err := p.DetermineLockTime()
1874+
require.NoError(t, err)
1875+
require.Equal(t, uint32(600000), lockTime) // Max height, NOT max time
1876+
})
1877+
1878+
t.Run("Conflicting requirements should error", func(t *testing.T) {
1879+
p, err := NewV2(2, 0, 0)
1880+
require.NoError(t, err)
1881+
1882+
txid, _ := chainhash.NewHashFromStr("2222222222222222222222222222222222222222222222222222222222222222")
1883+
1884+
// Input 1: Time-only (cannot satisfy height)
1885+
p.AddInput(*wire.NewOutPoint(txid, 0), 0)
1886+
p.Inputs[0].TimeLocktime = 1600000000
1887+
1888+
// Input 2: Height-only (cannot satisfy time)
1889+
p.AddInput(*wire.NewOutPoint(txid, 1), 0)
1890+
p.Inputs[1].HeightLocktime = 500000
1891+
1892+
// Should fail - conflicting requirements
1893+
_, err = p.DetermineLockTime()
1894+
require.Error(t, err)
1895+
require.Equal(t, ErrInvalidPsbtFormat, err)
1896+
})
1897+
1898+
t.Run("Fallback locktime when no constraints", func(t *testing.T) {
1899+
fallback := uint32(123456)
1900+
p, err := NewV2(2, fallback, 0)
1901+
require.NoError(t, err)
1902+
1903+
txid, _ := chainhash.NewHashFromStr("3333333333333333333333333333333333333333333333333333333333333333")
1904+
p.AddInput(*wire.NewOutPoint(txid, 0), 0)
1905+
// No TimeLocktime or HeightLocktime set
1906+
1907+
lockTime, err := p.DetermineLockTime()
1908+
require.NoError(t, err)
1909+
require.Equal(t, fallback, lockTime)
1910+
})
1911+
}
1912+
1913+
// TestPSBTv2AddUnknownFields tests the addUnknown field handling
1914+
func TestPSBTv2AddUnknownFields(t *testing.T) {
1915+
p, err := NewV2(2, 0, 0)
1916+
require.NoError(t, err)
1917+
1918+
txid, _ := chainhash.NewHashFromStr("4444444444444444444444444444444444444444444444444444444444444444")
1919+
p.AddInput(*wire.NewOutPoint(txid, 0), 0)
1920+
1921+
// Test adding unknown field succeeds
1922+
err = p.Inputs[0].addUnknown(0xfc, []byte{0x01, 0x02}, []byte{0x03, 0x04})
1923+
require.NoError(t, err)
1924+
require.Len(t, p.Inputs[0].Unknowns, 1)
1925+
1926+
// Test duplicate detection
1927+
err = p.Inputs[0].addUnknown(0xfc, []byte{0x01, 0x02}, []byte{0x03, 0x04})
1928+
require.Error(t, err)
1929+
require.Equal(t, ErrDuplicateKey, err)
1930+
1931+
p.AddOutput(1000000, []byte{0x76, 0xa9, 0x14})
1932+
1933+
// Test output unknown fields
1934+
err = p.Outputs[0].addUnknown(0xfd, []byte{0x05}, []byte{0x06})
1935+
require.NoError(t, err)
1936+
require.Len(t, p.Outputs[0].Unknowns, 1)
1937+
}

0 commit comments

Comments
 (0)