Skip to content

Commit ec1f5bc

Browse files
authored
Merge pull request #7357 from multiversx/revert-incoming-miniblocks
Revert incoming miniblocks
2 parents eed523d + 4853ab7 commit ec1f5bc

4 files changed

Lines changed: 273 additions & 3 deletions

File tree

process/block/gasConsumption.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package block
22

33
import (
4+
"bytes"
45
"fmt"
56
"sync"
67

78
"github.com/multiversx/mx-chain-core-go/core/check"
89
"github.com/multiversx/mx-chain-core-go/data"
910
"github.com/multiversx/mx-chain-go/process"
11+
"golang.org/x/exp/slices"
1012
)
1113

1214
// gasType defines the type of gas consumption
@@ -111,8 +113,12 @@ func (gc *gasConsumption) CheckIncomingMiniBlocks(
111113
for i := 0; i < len(miniBlocks); i++ {
112114
shouldSavePending, err = gc.checkIncomingMiniBlock(miniBlocks[i], transactions, bandwidthForIncomingMiniBlocks)
113115
if shouldSavePending {
116+
// saving pending starting with idx i, as it was not included either
114117
gc.pendingMiniBlocks = append(gc.pendingMiniBlocks, miniBlocks[i:]...)
115-
gc.transactionsForPendingMiniBlocks = transactions
118+
for _, mb := range miniBlocks[i:] {
119+
hashStr := string(mb.GetHash())
120+
gc.transactionsForPendingMiniBlocks[hashStr] = transactions[hashStr]
121+
}
116122

117123
return lastMiniBlockIndex, len(gc.pendingMiniBlocks), err
118124
}
@@ -125,6 +131,48 @@ func (gc *gasConsumption) CheckIncomingMiniBlocks(
125131
return lastMiniBlockIndex, 0, nil
126132
}
127133

134+
// RevertIncomingMiniBlocks gets a list of mini block hashes and removes them from the local state
135+
func (gc *gasConsumption) RevertIncomingMiniBlocks(miniBlockHashes [][]byte) {
136+
if len(miniBlockHashes) == 0 {
137+
return
138+
}
139+
140+
gc.mut.Lock()
141+
defer gc.mut.Unlock()
142+
143+
for _, miniBlockHash := range miniBlockHashes {
144+
// do not check here if it was found or not, as some pending mini blocks may be missing from this map
145+
gasConsumedByMb := gc.gasConsumedByMiniBlock[string(miniBlockHash)]
146+
delete(gc.gasConsumedByMiniBlock, string(miniBlockHash))
147+
148+
isPending, idxInPendingSlice := gc.isPendingMiniBlock(miniBlockHash)
149+
if isPending {
150+
gc.revertPendingMiniBlock(miniBlockHash, idxInPendingSlice)
151+
continue
152+
}
153+
154+
// if the mini block is not pending, remove it from the total gas consumed
155+
gc.totalGasConsumed[incoming] -= gasConsumedByMb
156+
}
157+
}
158+
159+
func (gc *gasConsumption) isPendingMiniBlock(blockHash []byte) (bool, int) {
160+
for idx, miniBlock := range gc.pendingMiniBlocks {
161+
if bytes.Equal(miniBlock.GetHash(), blockHash) {
162+
return true, idx
163+
}
164+
}
165+
166+
return false, initialLastIndex
167+
}
168+
169+
func (gc *gasConsumption) revertPendingMiniBlock(miniBlockHash []byte, idxInPendingSlice int) {
170+
// if the mini block was saved as pending, remove its transactions
171+
// and remove it from pending slice
172+
delete(gc.transactionsForPendingMiniBlocks, string(miniBlockHash))
173+
gc.pendingMiniBlocks = slices.Delete(gc.pendingMiniBlocks, idxInPendingSlice, idxInPendingSlice+1)
174+
}
175+
128176
func (gc *gasConsumption) checkIncomingMiniBlock(
129177
mb data.MiniBlockHeaderHandler,
130178
transactions map[string][]data.TransactionHandler,
@@ -153,13 +201,13 @@ func (gc *gasConsumption) checkIncomingMiniBlock(
153201
return false, err
154202
}
155203

204+
gc.gasConsumedByMiniBlock[string(mbHash)] = gasConsumedByMB
156205
mbsLimitReached := gc.totalGasConsumed[incoming]+gasConsumedByMB > bandwidthForIncomingMiniBlocks
157206
if !mbsLimitReached {
158207
// limit not reached, continue
159208
// this method might be called either from handling all mini blocks,
160209
// either from handling pending, where the pending ones
161210
// should have continuous indexes after the ones already included
162-
gc.gasConsumedByMiniBlock[string(mbHash)] = gasConsumedByMB
163211
gc.totalGasConsumed[incoming] += gasConsumedByMB
164212

165213
return false, nil

process/block/gasConsumption_test.go

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,13 +630,224 @@ func TestGasConsumption_ZeroIncomingLimit(t *testing.T) {
630630
require.Zero(t, pendingMBs) // added all
631631
}
632632

633+
func TestGasConsumption_RevertIncomingMiniBlocks(t *testing.T) {
634+
t.Parallel()
635+
636+
t.Run("empty mini block hashes should early exit", func(t *testing.T) {
637+
t.Parallel()
638+
639+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
640+
require.NotNil(t, gc)
641+
642+
gc.RevertIncomingMiniBlocks(nil)
643+
})
644+
645+
t.Run("revert non-pending mini block should decrease total gas consumed", func(t *testing.T) {
646+
t.Parallel()
647+
648+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
649+
require.NotNil(t, gc)
650+
651+
// add mini blocks that will be within limits
652+
mbs := generateMiniBlocks(3, 5)
653+
txs := generateTxsForMiniBlocks(mbs)
654+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
655+
require.NoError(t, err)
656+
require.Equal(t, 2, lastMbIndex)
657+
require.Zero(t, pendingMbs)
658+
659+
totalGasBeforeRevert := gc.TotalGasConsumed()
660+
expectedGasPerMb := maxGasLimitPerTx * 5 // 5 txs per mini block
661+
662+
// revert the first mini block and one none existing for coverage
663+
gc.RevertIncomingMiniBlocks([][]byte{mbs[0].GetHash(), []byte("non-existent-hash")})
664+
665+
// total gas should decrease by the gas consumed by the first mini block
666+
require.Equal(t, totalGasBeforeRevert-expectedGasPerMb, gc.TotalGasConsumed())
667+
668+
// revert the second and third mini blocks
669+
gc.RevertIncomingMiniBlocks([][]byte{mbs[1].GetHash(), mbs[2].GetHash()})
670+
671+
// total gas should be zero now
672+
require.Equal(t, uint64(0), gc.TotalGasConsumed())
673+
})
674+
675+
t.Run("revert pending mini block at first position", func(t *testing.T) {
676+
t.Parallel()
677+
678+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
679+
require.NotNil(t, gc)
680+
681+
// add more mini blocks than the limit to create pending ones
682+
mbs := generateMiniBlocks(10, 5)
683+
txs := generateTxsForMiniBlocks(mbs)
684+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
685+
require.NoError(t, err)
686+
require.Equal(t, 7, lastMbIndex)
687+
require.Equal(t, 2, pendingMbs)
688+
689+
// get pending mini blocks
690+
pendingMiniBlocks := gc.GetPendingMiniBlocks()
691+
require.Len(t, pendingMiniBlocks, 2)
692+
693+
totalGasBeforeRevert := gc.TotalGasConsumed()
694+
695+
// revert the first pending mini block
696+
firstPendingHash := mbs[8].GetHash()
697+
gc.RevertIncomingMiniBlocks([][]byte{firstPendingHash})
698+
699+
// total gas should stay the same, only a pending mini block was removed
700+
require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumed())
701+
702+
// check that pending mini blocks were updated
703+
updatedPendingMiniBlocks := gc.GetPendingMiniBlocks()
704+
require.Len(t, updatedPendingMiniBlocks, 1)
705+
require.Equal(t, mbs[9].GetHash(), updatedPendingMiniBlocks[0].GetHash())
706+
})
707+
708+
t.Run("revert pending mini block at last position", func(t *testing.T) {
709+
t.Parallel()
710+
711+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
712+
require.NotNil(t, gc)
713+
714+
// add more mini blocks than the limit to create pending ones
715+
mbs := generateMiniBlocks(10, 5)
716+
txs := generateTxsForMiniBlocks(mbs)
717+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
718+
require.NoError(t, err)
719+
require.Equal(t, 7, lastMbIndex)
720+
require.Equal(t, 2, pendingMbs)
721+
722+
// get pending mini blocks
723+
pendingMiniBlocks := gc.GetPendingMiniBlocks()
724+
require.Len(t, pendingMiniBlocks, 2)
725+
726+
totalGasBeforeRevert := gc.TotalGasConsumed()
727+
728+
// revert the last pending mini block (index 9, which is the last in pending)
729+
lastPendingHash := mbs[9].GetHash()
730+
gc.RevertIncomingMiniBlocks([][]byte{lastPendingHash})
731+
732+
// total gas should stay the same, only a pending mini block was removed
733+
require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumed())
734+
735+
// check that pending mini blocks were updated
736+
updatedPendingMiniBlocks := gc.GetPendingMiniBlocks()
737+
require.Len(t, updatedPendingMiniBlocks, 1)
738+
require.Equal(t, mbs[8].GetHash(), updatedPendingMiniBlocks[0].GetHash())
739+
})
740+
741+
t.Run("revert pending mini block at middle position", func(t *testing.T) {
742+
t.Parallel()
743+
744+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
745+
require.NotNil(t, gc)
746+
747+
// add more mini blocks than the limit to create 3 pending ones
748+
mbs := generateMiniBlocks(11, 5)
749+
txs := generateTxsForMiniBlocks(mbs)
750+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
751+
require.NoError(t, err)
752+
require.Equal(t, 7, lastMbIndex)
753+
require.Equal(t, 3, pendingMbs)
754+
755+
// get pending mini blocks
756+
pendingMiniBlocks := gc.GetPendingMiniBlocks()
757+
require.Len(t, pendingMiniBlocks, 3)
758+
759+
totalGasBeforeRevert := gc.TotalGasConsumed()
760+
761+
// revert the middle pending mini block (index 9, which is the middle in pending)
762+
middlePendingHash := mbs[9].GetHash()
763+
gc.RevertIncomingMiniBlocks([][]byte{middlePendingHash})
764+
765+
// total gas should stay the same, only a pending mini block was removed
766+
require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumed())
767+
768+
// check that pending mini blocks were updated
769+
updatedPendingMiniBlocks := gc.GetPendingMiniBlocks()
770+
require.Len(t, updatedPendingMiniBlocks, 2)
771+
require.Equal(t, mbs[8].GetHash(), updatedPendingMiniBlocks[0].GetHash())
772+
require.Equal(t, mbs[10].GetHash(), updatedPendingMiniBlocks[1].GetHash())
773+
})
774+
775+
t.Run("revert multiple mini blocks with mixed pending and non-pending", func(t *testing.T) {
776+
t.Parallel()
777+
778+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
779+
require.NotNil(t, gc)
780+
781+
// add mini blocks, some will be pending
782+
mbs := generateMiniBlocks(10, 5)
783+
txs := generateTxsForMiniBlocks(mbs)
784+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
785+
require.NoError(t, err)
786+
require.Equal(t, 7, lastMbIndex)
787+
require.Equal(t, 2, pendingMbs)
788+
789+
totalGasBeforeRevert := gc.TotalGasConsumed()
790+
expectedGasPerMb := maxGasLimitPerTx * 5
791+
792+
// revert both a non-pending mini block (index 0) and a pending one (index 8)
793+
hashesToRevert := [][]byte{
794+
mbs[0].GetHash(), // non-pending
795+
mbs[8].GetHash(), // pending (first in pending)
796+
}
797+
gc.RevertIncomingMiniBlocks(hashesToRevert)
798+
799+
// total gas should decrease only by the non-pending mini block
800+
require.Equal(t, totalGasBeforeRevert-expectedGasPerMb, gc.TotalGasConsumed())
801+
802+
// pending mini blocks should be reduced by one
803+
updatedPendingMiniBlocks := gc.GetPendingMiniBlocks()
804+
require.Len(t, updatedPendingMiniBlocks, 1)
805+
require.Equal(t, mbs[9].GetHash(), updatedPendingMiniBlocks[0].GetHash())
806+
})
807+
808+
t.Run("revert all pending mini blocks", func(t *testing.T) {
809+
t.Parallel()
810+
811+
gc, _ := block.NewGasConsumption(getMockArgsGasConsumption())
812+
require.NotNil(t, gc)
813+
814+
// add mini blocks with pending ones
815+
mbs := generateMiniBlocks(10, 5)
816+
txs := generateTxsForMiniBlocks(mbs)
817+
lastMbIndex, pendingMbs, err := gc.CheckIncomingMiniBlocks(mbs, txs)
818+
require.NoError(t, err)
819+
require.Equal(t, 7, lastMbIndex)
820+
require.Equal(t, 2, pendingMbs)
821+
822+
totalGasBeforeRevert := gc.TotalGasConsumed()
823+
824+
// revert all pending mini blocks
825+
hashesToRevert := [][]byte{
826+
mbs[8].GetHash(),
827+
mbs[9].GetHash(),
828+
}
829+
gc.RevertIncomingMiniBlocks(hashesToRevert)
830+
831+
// total gas should stay the same, only pending mini blocks were removed
832+
require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumed())
833+
834+
// no pending mini blocks should remain
835+
updatedPendingMiniBlocks := gc.GetPendingMiniBlocks()
836+
require.Len(t, updatedPendingMiniBlocks, 0)
837+
})
838+
}
839+
633840
func TestGasConsumption_ConcurrentOps(t *testing.T) {
634841
if testing.Short() {
635842
t.Skip("this is not a short test")
636843
}
637844

638845
require.NotPanics(t, func() {
639846
mbs := generateMiniBlocks(1, 2)
847+
mbsHashes := make([][]byte, len(mbs))
848+
for _, mb := range mbs {
849+
mbsHashes = append(mbsHashes, mb.GetHash())
850+
}
640851
txsInMBs := generateTxsForMiniBlocks(mbs)
641852

642853
txHashes, txs := generateTxs(maxGasLimitPerTx, 3)
@@ -650,7 +861,7 @@ func TestGasConsumption_ConcurrentOps(t *testing.T) {
650861

651862
for i := 0; i < numCalls; i++ {
652863
go func(idx int) {
653-
switch idx % 11 {
864+
switch idx % 12 {
654865
case 0:
655866
_, _, _ = gc.CheckOutgoingTransactions(txHashes, txs)
656867
case 1:
@@ -673,6 +884,8 @@ func TestGasConsumption_ConcurrentOps(t *testing.T) {
673884
gc.ZeroIncomingLimit()
674885
case 10:
675886
gc.ZeroOutgoingLimit()
887+
case 11:
888+
gc.RevertIncomingMiniBlocks(mbsHashes)
676889
default:
677890
require.Fail(t, "should have not been called")
678891
}

process/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,7 @@ type GasComputation interface {
15371537
txHashes [][]byte,
15381538
transactions []data.TransactionHandler,
15391539
) (addedTxHashes [][]byte, pendingMiniBlocksAdded []data.MiniBlockHeaderHandler, err error)
1540+
RevertIncomingMiniBlocks(miniBlockHashes [][]byte)
15401541
GetBandwidthForTransactions() uint64
15411542
TotalGasConsumed() uint64
15421543
DecreaseIncomingLimit()

testscommon/gasComputationMock.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type GasComputationMock struct {
1414
txHashes [][]byte,
1515
transactions []data.TransactionHandler,
1616
) ([][]byte, []data.MiniBlockHeaderHandler, error)
17+
RevertIncomingMiniBlocksCalled func(miniBlockHashes [][]byte)
1718
GetBandwidthForTransactionsCalled func() uint64
1819
TotalGasConsumedCalled func() uint64
1920
DecreaseIncomingLimitCalled func()
@@ -47,6 +48,13 @@ func (mock *GasComputationMock) CheckOutgoingTransactions(
4748
return nil, nil, nil
4849
}
4950

51+
// RevertIncomingMiniBlocks -
52+
func (mock *GasComputationMock) RevertIncomingMiniBlocks(miniBlockHashes [][]byte) {
53+
if mock.RevertIncomingMiniBlocksCalled != nil {
54+
mock.RevertIncomingMiniBlocksCalled(miniBlockHashes)
55+
}
56+
}
57+
5058
// GetBandwidthForTransactions -
5159
func (mock *GasComputationMock) GetBandwidthForTransactions() uint64 {
5260
if mock.GetBandwidthForTransactionsCalled != nil {

0 commit comments

Comments
 (0)