@@ -1536,6 +1536,225 @@ fn test_build_anchored_blocks_skip_too_expensive() {
1536
1536
}
1537
1537
}
1538
1538
1539
+ #[ test]
1540
+ fn test_build_anchored_blocks_mempool_fee_transaction_too_low ( ) {
1541
+ let privk = StacksPrivateKey :: from_hex (
1542
+ "42faca653724860da7a41bfcef7e6ba78db55146f6900de8cb2a9f760ffac70c01" ,
1543
+ )
1544
+ . unwrap ( ) ;
1545
+ let addr = StacksAddress :: from_public_keys (
1546
+ C32_ADDRESS_VERSION_TESTNET_SINGLESIG ,
1547
+ & AddressHashMode :: SerializeP2PKH ,
1548
+ 1 ,
1549
+ & vec ! [ StacksPublicKey :: from_private( & privk) ] ,
1550
+ )
1551
+ . unwrap ( ) ;
1552
+
1553
+ let mut peer_config = TestPeerConfig :: new ( function_name ! ( ) , 2032 , 2033 ) ;
1554
+ peer_config. initial_balances = vec ! [ ( addr. to_account_principal( ) , 1000000000 ) ] ;
1555
+ let burnchain = peer_config. burnchain . clone ( ) ;
1556
+
1557
+ let mut peer = TestPeer :: new ( peer_config) ;
1558
+
1559
+ let chainstate_path = peer. chainstate_path . clone ( ) ;
1560
+
1561
+ let recipient_addr_str = "ST1RFD5Q2QPK3E0F08HG9XDX7SSC7CNRS0QR0SGEV" ;
1562
+ let recipient = StacksAddress :: from_string ( recipient_addr_str) . unwrap ( ) ;
1563
+
1564
+ let tip =
1565
+ SortitionDB :: get_canonical_burn_chain_tip ( & peer. sortdb . as_ref ( ) . unwrap ( ) . conn ( ) ) . unwrap ( ) ;
1566
+
1567
+ let ( burn_ops, stacks_block, microblocks) = peer. make_tenure (
1568
+ |ref mut miner,
1569
+ ref mut sortdb,
1570
+ ref mut chainstate,
1571
+ vrf_proof,
1572
+ ref parent_opt,
1573
+ ref parent_microblock_header_opt| {
1574
+ let parent_tip = match parent_opt {
1575
+ None => StacksChainState :: get_genesis_header_info ( chainstate. db ( ) ) . unwrap ( ) ,
1576
+ Some ( block) => {
1577
+ let ic = sortdb. index_conn ( ) ;
1578
+ let snapshot = SortitionDB :: get_block_snapshot_for_winning_stacks_block (
1579
+ & ic,
1580
+ & tip. sortition_id ,
1581
+ & block. block_hash ( ) ,
1582
+ )
1583
+ . unwrap ( )
1584
+ . unwrap ( ) ;
1585
+ StacksChainState :: get_anchored_block_header_info (
1586
+ chainstate. db ( ) ,
1587
+ & snapshot. consensus_hash ,
1588
+ & snapshot. winning_stacks_block_hash ,
1589
+ )
1590
+ . unwrap ( )
1591
+ . unwrap ( )
1592
+ }
1593
+ } ;
1594
+
1595
+ let parent_header_hash = parent_tip. anchored_header . block_hash ( ) ;
1596
+ let parent_consensus_hash = parent_tip. consensus_hash . clone ( ) ;
1597
+
1598
+ let mut mempool = MemPoolDB :: open_test ( false , 0x80000000 , & chainstate_path) . unwrap ( ) ;
1599
+
1600
+ let coinbase_tx = make_coinbase ( miner, 0 ) ;
1601
+
1602
+ // Create a zero-fee transaction
1603
+ let zero_fee_tx = make_user_stacks_transfer (
1604
+ & privk,
1605
+ 0 ,
1606
+ 0 , // Set fee to 0
1607
+ & recipient. to_account_principal ( ) ,
1608
+ 1000 ,
1609
+ ) ;
1610
+
1611
+ let result = mempool. submit (
1612
+ chainstate,
1613
+ sortdb,
1614
+ & parent_consensus_hash,
1615
+ & parent_header_hash,
1616
+ & zero_fee_tx,
1617
+ None ,
1618
+ & ExecutionCost :: max_value ( ) ,
1619
+ & StacksEpochId :: Epoch20 ,
1620
+ ) ;
1621
+
1622
+ match result {
1623
+ Ok ( _) => panic ! ( "Expected FeeTooLow error but transaction was accepted" ) ,
1624
+ Err ( e) => match e {
1625
+ MemPoolRejection :: FeeTooLow ( actual, required) => {
1626
+ assert_eq ! ( actual, 0 ) ;
1627
+ assert_eq ! ( required, 180 ) ;
1628
+ }
1629
+ _ => panic ! ( "Unexpected error: {:?}" , e) ,
1630
+ } ,
1631
+ } ;
1632
+
1633
+ let anchored_block = StacksBlockBuilder :: build_anchored_block (
1634
+ chainstate,
1635
+ & sortdb. index_handle_at_tip ( ) ,
1636
+ & mut mempool,
1637
+ & parent_tip,
1638
+ tip. total_burn ,
1639
+ vrf_proof,
1640
+ Hash160 ( [ 0 as u8 ; 20 ] ) ,
1641
+ & coinbase_tx,
1642
+ BlockBuilderSettings :: max_value ( ) ,
1643
+ None ,
1644
+ & burnchain,
1645
+ )
1646
+ . unwrap ( ) ;
1647
+
1648
+ ( anchored_block. 0 , vec ! [ ] )
1649
+ } ,
1650
+ ) ;
1651
+
1652
+ peer. next_burnchain_block ( burn_ops. clone ( ) ) ;
1653
+ peer. process_stacks_epoch_at_tip ( & stacks_block, & microblocks) ;
1654
+
1655
+ // Check that the block contains only coinbase transactions (coinbase)
1656
+ assert_eq ! ( stacks_block. txs. len( ) , 1 ) ;
1657
+ }
1658
+
1659
+ #[ test]
1660
+ fn test_build_anchored_blocks_zero_fee_transaction ( ) {
1661
+ let privk = StacksPrivateKey :: from_hex (
1662
+ "42faca653724860da7a41bfcef7e6ba78db55146f6900de8cb2a9f760ffac70c01" ,
1663
+ )
1664
+ . unwrap ( ) ;
1665
+ let addr = StacksAddress :: from_public_keys (
1666
+ C32_ADDRESS_VERSION_TESTNET_SINGLESIG ,
1667
+ & AddressHashMode :: SerializeP2PKH ,
1668
+ 1 ,
1669
+ & vec ! [ StacksPublicKey :: from_private( & privk) ] ,
1670
+ )
1671
+ . unwrap ( ) ;
1672
+
1673
+ let mut peer_config = TestPeerConfig :: new ( function_name ! ( ) , 2032 , 2033 ) ;
1674
+ peer_config. initial_balances = vec ! [ ( addr. to_account_principal( ) , 1000000000 ) ] ;
1675
+ let burnchain = peer_config. burnchain . clone ( ) ;
1676
+
1677
+ let mut peer = TestPeer :: new ( peer_config) ;
1678
+
1679
+ let chainstate_path = peer. chainstate_path . clone ( ) ;
1680
+
1681
+ let recipient_addr_str = "ST1RFD5Q2QPK3E0F08HG9XDX7SSC7CNRS0QR0SGEV" ;
1682
+ let recipient = StacksAddress :: from_string ( recipient_addr_str) . unwrap ( ) ;
1683
+
1684
+ let tip =
1685
+ SortitionDB :: get_canonical_burn_chain_tip ( & peer. sortdb . as_ref ( ) . unwrap ( ) . conn ( ) ) . unwrap ( ) ;
1686
+
1687
+ let ( burn_ops, stacks_block, microblocks) = peer. make_tenure (
1688
+ |ref mut miner,
1689
+ ref mut sortdb,
1690
+ ref mut chainstate,
1691
+ vrf_proof,
1692
+ ref parent_opt,
1693
+ ref parent_microblock_header_opt| {
1694
+ let parent_tip = match parent_opt {
1695
+ None => StacksChainState :: get_genesis_header_info ( chainstate. db ( ) ) . unwrap ( ) ,
1696
+ Some ( block) => {
1697
+ let ic = sortdb. index_conn ( ) ;
1698
+ let snapshot = SortitionDB :: get_block_snapshot_for_winning_stacks_block (
1699
+ & ic,
1700
+ & tip. sortition_id ,
1701
+ & block. block_hash ( ) ,
1702
+ )
1703
+ . unwrap ( )
1704
+ . unwrap ( ) ;
1705
+ StacksChainState :: get_anchored_block_header_info (
1706
+ chainstate. db ( ) ,
1707
+ & snapshot. consensus_hash ,
1708
+ & snapshot. winning_stacks_block_hash ,
1709
+ )
1710
+ . unwrap ( )
1711
+ . unwrap ( )
1712
+ }
1713
+ } ;
1714
+
1715
+ let coinbase_tx = make_coinbase ( miner, 0 ) ;
1716
+
1717
+ // Create a zero-fee transaction
1718
+ let zero_fee_tx = make_user_stacks_transfer (
1719
+ & privk,
1720
+ 0 ,
1721
+ 0 , // Set fee to 0
1722
+ & recipient. to_account_principal ( ) ,
1723
+ 1000 ,
1724
+ ) ;
1725
+
1726
+ let block_builder = StacksBlockBuilder :: make_regtest_block_builder (
1727
+ & burnchain,
1728
+ & parent_tip,
1729
+ vrf_proof,
1730
+ tip. total_burn ,
1731
+ Hash160 ( [ 0 as u8 ; 20 ] ) ,
1732
+ )
1733
+ . unwrap ( ) ;
1734
+
1735
+ let anchored_block = StacksBlockBuilder :: make_anchored_block_from_txs (
1736
+ block_builder,
1737
+ chainstate,
1738
+ & sortdb. index_handle_at_tip ( ) ,
1739
+ vec ! [ coinbase_tx, zero_fee_tx] ,
1740
+ )
1741
+ . unwrap ( ) ;
1742
+
1743
+ ( anchored_block. 0 , vec ! [ ] )
1744
+ } ,
1745
+ ) ;
1746
+
1747
+ peer. next_burnchain_block ( burn_ops. clone ( ) ) ;
1748
+ peer. process_stacks_epoch_at_tip ( & stacks_block, & microblocks) ;
1749
+
1750
+ // Check that the block contains 2 transactions (coinbase + zero-fee transaction)
1751
+ assert_eq ! ( stacks_block. txs. len( ) , 2 ) ;
1752
+
1753
+ // Verify that the zero-fee transaction is in the block
1754
+ let zero_fee_tx = & stacks_block. txs [ 1 ] ;
1755
+ assert_eq ! ( zero_fee_tx. get_tx_fee( ) , 0 ) ;
1756
+ }
1757
+
1539
1758
#[ test]
1540
1759
fn test_build_anchored_blocks_multiple_chaintips ( ) {
1541
1760
let mut privks = vec ! [ ] ;
0 commit comments