github.com/amazechain/amc@v0.1.3/modules/ethdb/olddb/mapmutation.go (about)

     1  package olddb
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"github.com/amazechain/amc/modules"
     8  	"sync"
     9  	"time"
    10  	"unsafe"
    11  
    12  	"github.com/ledgerwatch/erigon-lib/etl"
    13  	"github.com/ledgerwatch/erigon-lib/kv"
    14  	"github.com/ledgerwatch/log/v3"
    15  
    16  	"github.com/amazechain/amc/modules/ethdb"
    17  )
    18  
    19  type mapmutation struct {
    20  	puts   map[string]map[string][]byte // table -> key -> value ie. blocks -> hash -> blockBod
    21  	db     kv.RwTx
    22  	quit   <-chan struct{}
    23  	clean  func()
    24  	mu     sync.RWMutex
    25  	size   int
    26  	count  uint64
    27  	tmpdir string
    28  }
    29  
    30  // NewBatch - starts in-mem batch
    31  //
    32  // Common pattern:
    33  //
    34  // batch := db.NewBatch()
    35  // defer batch.Rollback()
    36  // ... some calculations on `batch`
    37  // batch.Commit()
    38  func NewHashBatch(tx kv.RwTx, quit <-chan struct{}, tmpdir string) *mapmutation {
    39  	clean := func() {}
    40  	if quit == nil {
    41  		ch := make(chan struct{})
    42  		clean = func() { close(ch) }
    43  		quit = ch
    44  	}
    45  
    46  	return &mapmutation{
    47  		db:     tx,
    48  		puts:   make(map[string]map[string][]byte),
    49  		quit:   quit,
    50  		clean:  clean,
    51  		tmpdir: tmpdir,
    52  	}
    53  }
    54  
    55  func (m *mapmutation) RwKV() kv.RwDB {
    56  	if casted, ok := m.db.(ethdb.HasRwKV); ok {
    57  		return casted.RwKV()
    58  	}
    59  	return nil
    60  }
    61  
    62  func (m *mapmutation) getMem(table string, key []byte) ([]byte, bool) {
    63  	m.mu.RLock()
    64  	defer m.mu.RUnlock()
    65  	if _, ok := m.puts[table]; !ok {
    66  		return nil, false
    67  	}
    68  	if value, ok := m.puts[table][*(*string)(unsafe.Pointer(&key))]; ok {
    69  		return value, ok
    70  	}
    71  
    72  	return nil, false
    73  }
    74  
    75  func (m *mapmutation) IncrementSequence(bucket string, amount uint64) (res uint64, err error) {
    76  	v, ok := m.getMem(modules.Sequence, []byte(bucket))
    77  	if !ok && m.db != nil {
    78  		v, err = m.db.GetOne(modules.Sequence, []byte(bucket))
    79  		if err != nil {
    80  			return 0, err
    81  		}
    82  	}
    83  
    84  	var currentV uint64 = 0
    85  	if len(v) > 0 {
    86  		currentV = binary.BigEndian.Uint64(v)
    87  	}
    88  
    89  	newVBytes := make([]byte, 8)
    90  	binary.BigEndian.PutUint64(newVBytes, currentV+amount)
    91  	if err = m.Put(modules.Sequence, []byte(bucket), newVBytes); err != nil {
    92  		return 0, err
    93  	}
    94  
    95  	return currentV, nil
    96  }
    97  func (m *mapmutation) ReadSequence(bucket string) (res uint64, err error) {
    98  	v, ok := m.getMem(modules.Sequence, []byte(bucket))
    99  	if !ok && m.db != nil {
   100  		v, err = m.db.GetOne(modules.Sequence, []byte(bucket))
   101  		if err != nil {
   102  			return 0, err
   103  		}
   104  	}
   105  	var currentV uint64 = 0
   106  	if len(v) > 0 {
   107  		currentV = binary.BigEndian.Uint64(v)
   108  	}
   109  
   110  	return currentV, nil
   111  }
   112  
   113  // Can only be called from the worker thread
   114  func (m *mapmutation) GetOne(table string, key []byte) ([]byte, error) {
   115  	if value, ok := m.getMem(table, key); ok {
   116  		return value, nil
   117  	}
   118  	if m.db != nil {
   119  		// TODO: simplify when tx can no longer be parent of mutation
   120  		value, err := m.db.GetOne(table, key)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		return value, nil
   125  	}
   126  	return nil, nil
   127  }
   128  
   129  // Can only be called from the worker thread
   130  func (m *mapmutation) Get(table string, key []byte) ([]byte, error) {
   131  	value, err := m.GetOne(table, key)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if value == nil {
   137  		return nil, ethdb.ErrKeyNotFound
   138  	}
   139  
   140  	return value, nil
   141  }
   142  
   143  func (m *mapmutation) Last(table string) ([]byte, []byte, error) {
   144  	c, err := m.db.Cursor(table)
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  	defer c.Close()
   149  	return c.Last()
   150  }
   151  
   152  func (m *mapmutation) Has(table string, key []byte) (bool, error) {
   153  	if _, ok := m.getMem(table, key); ok {
   154  		return ok, nil
   155  	}
   156  	if m.db != nil {
   157  		return m.db.Has(table, key)
   158  	}
   159  	return false, nil
   160  }
   161  
   162  // puts a table key with a value and if the table is not found then it appends a table
   163  func (m *mapmutation) Put(table string, k, v []byte) error {
   164  	m.mu.Lock()
   165  	defer m.mu.Unlock()
   166  	if _, ok := m.puts[table]; !ok {
   167  		m.puts[table] = make(map[string][]byte)
   168  	}
   169  
   170  	stringKey := string(k)
   171  
   172  	var ok bool
   173  	if _, ok = m.puts[table][stringKey]; !ok {
   174  		m.size += len(v) - len(m.puts[table][stringKey])
   175  		m.puts[table][stringKey] = v
   176  		return nil
   177  	}
   178  	m.puts[table][stringKey] = v
   179  	m.size += len(k) + len(v)
   180  	m.count++
   181  
   182  	return nil
   183  }
   184  
   185  func (m *mapmutation) Append(table string, key []byte, value []byte) error {
   186  	return m.Put(table, key, value)
   187  }
   188  
   189  func (m *mapmutation) AppendDup(table string, key []byte, value []byte) error {
   190  	return m.Put(table, key, value)
   191  }
   192  
   193  func (m *mapmutation) BatchSize() int {
   194  	m.mu.RLock()
   195  	defer m.mu.RUnlock()
   196  	return m.size
   197  }
   198  
   199  func (m *mapmutation) ForEach(bucket string, fromPrefix []byte, walker func(k, v []byte) error) error {
   200  	m.panicOnEmptyDB()
   201  	return m.db.ForEach(bucket, fromPrefix, walker)
   202  }
   203  
   204  func (m *mapmutation) ForPrefix(bucket string, prefix []byte, walker func(k, v []byte) error) error {
   205  	m.panicOnEmptyDB()
   206  	return m.db.ForPrefix(bucket, prefix, walker)
   207  }
   208  
   209  func (m *mapmutation) ForAmount(bucket string, prefix []byte, amount uint32, walker func(k, v []byte) error) error {
   210  	m.panicOnEmptyDB()
   211  	return m.db.ForAmount(bucket, prefix, amount, walker)
   212  }
   213  
   214  func (m *mapmutation) Delete(table string, k []byte) error {
   215  	return m.Put(table, k, nil)
   216  }
   217  
   218  func (m *mapmutation) doCommit(tx kv.RwTx) error {
   219  	logEvery := time.NewTicker(30 * time.Second)
   220  	defer logEvery.Stop()
   221  	count := 0
   222  	total := float64(m.count)
   223  	for table, bucket := range m.puts {
   224  		logger := log.New()
   225  		collector := etl.NewCollector("", m.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize), logger)
   226  		defer collector.Close()
   227  		for key, value := range bucket {
   228  			collector.Collect([]byte(key), value)
   229  			count++
   230  			select {
   231  			default:
   232  			case <-logEvery.C:
   233  				progress := fmt.Sprintf("%.1fM/%.1fM", float64(count)/1_000_000, total/1_000_000)
   234  				log.Info("Write to db", "progress", progress, "current table", table)
   235  				tx.CollectMetrics()
   236  			}
   237  		}
   238  		if err := collector.Load(m.db, table, etl.IdentityLoadFunc, etl.TransformArgs{Quit: m.quit}); err != nil {
   239  			return err
   240  		}
   241  	}
   242  
   243  	tx.CollectMetrics()
   244  	return nil
   245  }
   246  
   247  func (m *mapmutation) Commit() error {
   248  	if m.db == nil {
   249  		return nil
   250  	}
   251  	m.mu.Lock()
   252  	defer m.mu.Unlock()
   253  	if err := m.doCommit(m.db); err != nil {
   254  		return err
   255  	}
   256  
   257  	m.puts = map[string]map[string][]byte{}
   258  	m.size = 0
   259  	m.count = 0
   260  	m.clean()
   261  	return nil
   262  }
   263  
   264  func (m *mapmutation) Rollback() {
   265  	m.mu.Lock()
   266  	defer m.mu.Unlock()
   267  	m.puts = map[string]map[string][]byte{}
   268  	m.size = 0
   269  	m.count = 0
   270  	m.size = 0
   271  	m.clean()
   272  }
   273  
   274  func (m *mapmutation) Close() {
   275  	m.Rollback()
   276  }
   277  
   278  func (m *mapmutation) Begin(ctx context.Context, flags ethdb.TxFlags) (ethdb.DbWithPendingMutations, error) {
   279  	panic("mutation can't start transaction, because doesn't own it")
   280  }
   281  
   282  func (m *mapmutation) panicOnEmptyDB() {
   283  	if m.db == nil {
   284  		panic("Not implemented")
   285  	}
   286  }
   287  
   288  func (m *mapmutation) SetRwKV(kv kv.RwDB) {
   289  	hasRwKV, ok := m.db.(ethdb.HasRwKV)
   290  	if !ok {
   291  		log.Warn("Failed to convert mapmutation type to HasRwKV interface")
   292  	}
   293  	hasRwKV.SetRwKV(kv)
   294  }