Skip to content

Commit 8c12f51

Browse files
Close cursor if it's opened by different tx (#12546)
So we keep cursor interface opened inside `DomainRoTx`. If we then create new tx and try to read with it from `DomainRoTx`, value will be fetched via previously opened cursor which is not expected behaviour. I set stupid fix for that but i assume there could be better solutions. --------- Co-authored-by: alex.sharov <[email protected]>
1 parent 7c5dead commit 8c12f51

File tree

9 files changed

+346
-20
lines changed

9 files changed

+346
-20
lines changed

core/rawdb/rawtemporaldb/accessors_receipt_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ func TestAppendReceipt(t *testing.T) {
2020
require.NoError(err)
2121
defer tx.Rollback()
2222

23-
doms, err := state.NewSharedDomains(tx, log.New())
23+
ttx := tx.(kv.TemporalTx)
24+
doms, err := state.NewSharedDomains(ttx, log.New())
2425
require.NoError(err)
2526
defer doms.Close()
26-
doms.SetTx(tx)
27+
doms.SetTx(ttx)
2728

2829
doms.SetTxNum(0) // block1
2930
err = AppendReceipt(doms, &types.Receipt{CumulativeGasUsed: 10, FirstLogIndexWithinBlock: 0}, 0)
@@ -48,7 +49,6 @@ func TestAppendReceipt(t *testing.T) {
4849
err = doms.Flush(context.Background(), tx)
4950
require.NoError(err)
5051

51-
ttx := tx.(kv.TemporalTx)
5252
v, ok, err := ttx.HistorySeek(kv.ReceiptDomain, FirstLogIndexKey, 0)
5353
require.NoError(err)
5454
require.True(ok)

core/vm/gas_table_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ package vm_test
2222
import (
2323
"context"
2424
"errors"
25+
"fmt"
2526
"math"
2627
"strconv"
2728
"testing"
29+
"unsafe"
2830

2931
"github.com/holiman/uint256"
3032
"github.com/stretchr/testify/require"
@@ -195,12 +197,16 @@ func TestCreateGas(t *testing.T) {
195197
var txc wrap.TxContainer
196198
txc.Tx = tx
197199

198-
domains, err := state3.NewSharedDomains(tx, log.New())
200+
eface := *(*[2]uintptr)(unsafe.Pointer(&tx))
201+
fmt.Printf("init tx %x\n", eface[1])
202+
203+
domains, err := state3.NewSharedDomains(txc.Tx, log.New())
199204
require.NoError(t, err)
200205
defer domains.Close()
201206
txc.Doms = domains
202207

203-
stateReader = rpchelper.NewLatestStateReader(tx)
208+
//stateReader = rpchelper.NewLatestStateReader(domains)
209+
stateReader = rpchelper.NewLatestDomainStateReader(domains)
204210
stateWriter = rpchelper.NewLatestStateWriter(txc, nil, 0)
205211

206212
s := state.New(stateReader)
@@ -230,5 +236,6 @@ func TestCreateGas(t *testing.T) {
230236
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed)
231237
}
232238
tx.Rollback()
239+
domains.Close()
233240
}
234241
}

erigon-lib/kv/mdbx/kv_mdbx.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ func (tx *MdbxTx) stdCursor(bucket string) (kv.RwCursor, error) {
11191119
if tx.toCloseMap == nil {
11201120
tx.toCloseMap = make(map[uint64]kv.Closer)
11211121
}
1122-
tx.toCloseMap[c.id] = c.c
1122+
tx.toCloseMap[c.id] = c
11231123
return c, nil
11241124
}
11251125

@@ -1268,6 +1268,8 @@ func (c *MdbxCursor) Close() {
12681268
}
12691269
}
12701270

1271+
func (c *MdbxCursor) IsClosed() bool { return c.c == nil }
1272+
12711273
type MdbxDupSortCursor struct {
12721274
*MdbxCursor
12731275
}

erigon-lib/state/aggregator_test.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,213 @@ import (
5353
"github.com/stretchr/testify/require"
5454
)
5555

56+
func TestAggregatorV3_Merge(t *testing.T) {
57+
t.Parallel()
58+
db, agg := testDbAndAggregatorv3(t, 10)
59+
rwTx, err := db.BeginRwNosync(context.Background())
60+
require.NoError(t, err)
61+
defer func() {
62+
if rwTx != nil {
63+
rwTx.Rollback()
64+
}
65+
}()
66+
67+
ac := agg.BeginFilesRo()
68+
defer ac.Close()
69+
domains, err := NewSharedDomains(WrapTxWithCtx(rwTx, ac), log.New())
70+
require.NoError(t, err)
71+
defer domains.Close()
72+
73+
txs := uint64(1000)
74+
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
75+
76+
var (
77+
commKey1 = []byte("someCommKey")
78+
commKey2 = []byte("otherCommKey")
79+
)
80+
81+
// keys are encodings of numbers 1..31
82+
// each key changes value on every txNum which is multiple of the key
83+
var maxWrite, otherMaxWrite uint64
84+
for txNum := uint64(1); txNum <= txs; txNum++ {
85+
domains.SetTxNum(txNum)
86+
87+
addr, loc := make([]byte, length.Addr), make([]byte, length.Hash)
88+
89+
n, err := rnd.Read(addr)
90+
require.NoError(t, err)
91+
require.EqualValues(t, length.Addr, n)
92+
93+
n, err = rnd.Read(loc)
94+
require.NoError(t, err)
95+
require.EqualValues(t, length.Hash, n)
96+
97+
buf := types.EncodeAccountBytesV3(1, uint256.NewInt(0), nil, 0)
98+
err = domains.DomainPut(kv.AccountsDomain, addr, nil, buf, nil, 0)
99+
require.NoError(t, err)
100+
101+
err = domains.DomainPut(kv.StorageDomain, addr, loc, []byte{addr[0], loc[0]}, nil, 0)
102+
require.NoError(t, err)
103+
104+
var v [8]byte
105+
binary.BigEndian.PutUint64(v[:], txNum)
106+
if txNum%135 == 0 {
107+
pv, step, err := domains.GetLatest(kv.CommitmentDomain, commKey2)
108+
require.NoError(t, err)
109+
110+
err = domains.DomainPut(kv.CommitmentDomain, commKey2, nil, v[:], pv, step)
111+
require.NoError(t, err)
112+
otherMaxWrite = txNum
113+
} else {
114+
pv, step, err := domains.GetLatest(kv.CommitmentDomain, commKey1)
115+
require.NoError(t, err)
116+
117+
err = domains.DomainPut(kv.CommitmentDomain, commKey1, nil, v[:], pv, step)
118+
require.NoError(t, err)
119+
maxWrite = txNum
120+
}
121+
require.NoError(t, err)
122+
123+
}
124+
125+
err = domains.Flush(context.Background(), rwTx)
126+
require.NoError(t, err)
127+
128+
require.NoError(t, err)
129+
err = rwTx.Commit()
130+
require.NoError(t, err)
131+
rwTx = nil
132+
133+
err = agg.BuildFiles(txs)
134+
require.NoError(t, err)
135+
136+
rwTx, err = db.BeginRw(context.Background())
137+
require.NoError(t, err)
138+
defer rwTx.Rollback()
139+
140+
logEvery := time.NewTicker(30 * time.Second)
141+
defer logEvery.Stop()
142+
stat, err := ac.Prune(context.Background(), rwTx, 0, logEvery)
143+
require.NoError(t, err)
144+
t.Logf("Prune: %s", stat)
145+
146+
err = rwTx.Commit()
147+
require.NoError(t, err)
148+
149+
err = agg.MergeLoop(context.Background())
150+
require.NoError(t, err)
151+
152+
// Check the history
153+
roTx, err := db.BeginRo(context.Background())
154+
require.NoError(t, err)
155+
defer roTx.Rollback()
156+
157+
dc := agg.BeginFilesRo()
158+
159+
v, _, ex, err := dc.GetLatest(kv.CommitmentDomain, commKey1, roTx)
160+
require.NoError(t, err)
161+
require.Truef(t, ex, "key %x not found", commKey1)
162+
163+
require.EqualValues(t, maxWrite, binary.BigEndian.Uint64(v[:]))
164+
165+
v, _, ex, err = dc.GetLatest(kv.CommitmentDomain, commKey2, roTx)
166+
require.NoError(t, err)
167+
require.Truef(t, ex, "key %x not found", commKey2)
168+
dc.Close()
169+
170+
require.EqualValues(t, otherMaxWrite, binary.BigEndian.Uint64(v[:]))
171+
}
172+
173+
func TestAggregatorV3_MergeValTransform(t *testing.T) {
174+
t.Parallel()
175+
db, agg := testDbAndAggregatorv3(t, 10)
176+
rwTx, err := db.BeginRwNosync(context.Background())
177+
require.NoError(t, err)
178+
defer func() {
179+
if rwTx != nil {
180+
rwTx.Rollback()
181+
}
182+
}()
183+
ac := agg.BeginFilesRo()
184+
defer ac.Close()
185+
domains, err := NewSharedDomains(WrapTxWithCtx(rwTx, ac), log.New())
186+
require.NoError(t, err)
187+
defer domains.Close()
188+
189+
txs := uint64(1000)
190+
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
191+
192+
agg.commitmentValuesTransform = true
193+
194+
state := make(map[string][]byte)
195+
196+
// keys are encodings of numbers 1..31
197+
// each key changes value on every txNum which is multiple of the key
198+
//var maxWrite, otherMaxWrite uint64
199+
for txNum := uint64(1); txNum <= txs; txNum++ {
200+
domains.SetTxNum(txNum)
201+
202+
addr, loc := make([]byte, length.Addr), make([]byte, length.Hash)
203+
204+
n, err := rnd.Read(addr)
205+
require.NoError(t, err)
206+
require.EqualValues(t, length.Addr, n)
207+
208+
n, err = rnd.Read(loc)
209+
require.NoError(t, err)
210+
require.EqualValues(t, length.Hash, n)
211+
212+
buf := types.EncodeAccountBytesV3(1, uint256.NewInt(txNum*1e6), nil, 0)
213+
err = domains.DomainPut(kv.AccountsDomain, addr, nil, buf, nil, 0)
214+
require.NoError(t, err)
215+
216+
err = domains.DomainPut(kv.StorageDomain, addr, loc, []byte{addr[0], loc[0]}, nil, 0)
217+
require.NoError(t, err)
218+
219+
if (txNum+1)%agg.StepSize() == 0 {
220+
_, err := domains.ComputeCommitment(context.Background(), true, txNum/10, "")
221+
require.NoError(t, err)
222+
}
223+
224+
state[string(addr)] = buf
225+
state[string(addr)+string(loc)] = []byte{addr[0], loc[0]}
226+
}
227+
228+
err = domains.Flush(context.Background(), rwTx)
229+
require.NoError(t, err)
230+
231+
err = rwTx.Commit()
232+
require.NoError(t, err)
233+
rwTx = nil
234+
235+
err = agg.BuildFiles(txs)
236+
require.NoError(t, err)
237+
238+
ac.Close()
239+
ac = agg.BeginFilesRo()
240+
defer ac.Close()
241+
242+
rwTx, err = db.BeginRwNosync(context.Background())
243+
require.NoError(t, err)
244+
defer func() {
245+
if rwTx != nil {
246+
rwTx.Rollback()
247+
}
248+
}()
249+
250+
logEvery := time.NewTicker(30 * time.Second)
251+
defer logEvery.Stop()
252+
stat, err := ac.Prune(context.Background(), rwTx, 0, logEvery)
253+
require.NoError(t, err)
254+
t.Logf("Prune: %s", stat)
255+
256+
err = rwTx.Commit()
257+
require.NoError(t, err)
258+
259+
err = agg.MergeLoop(context.Background())
260+
require.NoError(t, err)
261+
}
262+
56263
func TestAggregatorV3_RestartOnDatadir(t *testing.T) {
57264
t.Parallel()
58265
//t.Skip()

erigon-lib/state/domain.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,8 @@ type DomainRoTx struct {
654654
keyBuf [60]byte // 52b key and 8b for inverted step
655655
comBuf []byte
656656

657-
valsC kv.Cursor
657+
valsC kv.Cursor
658+
valCViewID uint64 // to make sure that valsC reading from the same view with given kv.Tx
658659

659660
getFromFileCache *DomainGetFromFileCache
660661
}
@@ -1638,6 +1639,7 @@ func (dt *DomainRoTx) Close() {
16381639
if dt.files == nil { // invariant: it's safe to call Close multiple times
16391640
return
16401641
}
1642+
dt.closeValsCursor()
16411643
files := dt.files
16421644
dt.files = nil
16431645
for i := range files {
@@ -1705,12 +1707,46 @@ func (dt *DomainRoTx) statelessBtree(i int) *BtIndex {
17051707
return r
17061708
}
17071709

1708-
func (dt *DomainRoTx) valsCursor(tx kv.Tx) (c kv.Cursor, err error) {
1710+
var sdTxImmutabilityInvariant = errors.New("tx passed into ShredDomains is immutable")
1711+
1712+
func (dt *DomainRoTx) closeValsCursor() {
17091713
if dt.valsC != nil {
17101714
dt.valsC.Close()
1715+
dt.valCViewID = 0
17111716
dt.valsC = nil
1717+
// dt.vcParentPtr.Store(0)
1718+
}
1719+
}
1720+
1721+
type canCheckClosed interface {
1722+
IsClosed() bool
1723+
}
1724+
1725+
func (dt *DomainRoTx) valsCursor(tx kv.Tx) (c kv.Cursor, err error) {
1726+
if dt.valsC != nil { // run in assert mode only
1727+
if asserts {
1728+
if tx.ViewID() != dt.valCViewID {
1729+
panic(fmt.Errorf("%w: DomainRoTx=%s cursor ViewID=%d; given tx.ViewID=%d", sdTxImmutabilityInvariant, dt.d.filenameBase, dt.valCViewID, tx.ViewID())) // cursor opened by different tx, invariant broken
1730+
}
1731+
if mc, ok := dt.valsC.(canCheckClosed); !ok && mc.IsClosed() {
1732+
panic(fmt.Sprintf("domainRoTx=%s cursor lives longer than Cursor (=> than tx opened that cursor)", dt.d.filenameBase))
1733+
}
1734+
// if dt.d.largeValues {
1735+
// if mc, ok := dt.valsC.(*mdbx.MdbxCursor); ok && mc.IsClosed() {
1736+
// panic(fmt.Sprintf("domainRoTx=%s cursor lives longer than Cursor (=> than tx opened that cursor)", dt.d.filenameBase))
1737+
// }
1738+
// } else {
1739+
// if mc, ok := dt.valsC.(*mdbx.MdbxDupSortCursor); ok && mc.IsClosed() {
1740+
// panic(fmt.Sprintf("domainRoTx=%s cursor lives longer than DupCursor (=> than tx opened that cursor)", dt.d.filenameBase))
1741+
// }
1742+
// }
1743+
}
1744+
return dt.valsC, nil
17121745
}
17131746

1747+
if asserts {
1748+
dt.valCViewID = tx.ViewID()
1749+
}
17141750
if dt.d.largeValues {
17151751
dt.valsC, err = tx.Cursor(dt.d.valuesTable)
17161752
return dt.valsC, err

erigon-lib/state/domain_shared.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,13 +915,14 @@ func (sd *SharedDomains) Flush(ctx context.Context, tx kv.RwTx) error {
915915
_, f, l, _ := runtime.Caller(1)
916916
fmt.Printf("[SD aggTx=%d] FLUSHING at tx %d [%x], caller %s:%d\n", sd.aggTx.id, sd.TxNum(), fh, filepath.Base(f), l)
917917
}
918-
for _, w := range sd.domainWriters {
918+
for di, w := range sd.domainWriters {
919919
if w == nil {
920920
continue
921921
}
922922
if err := w.Flush(ctx, tx); err != nil {
923923
return err
924924
}
925+
sd.aggTx.d[di].closeValsCursor()
925926
}
926927
for _, w := range sd.iiWriters {
927928
if w == nil {

0 commit comments

Comments
 (0)