erigon-pulse/migrations/migrations.go

275 lines
7.7 KiB
Go
Raw Permalink Normal View History

package migrations
import (
"bytes"
bitmap indices for logs (#1124) * save progress * try now * don't create bloom inside rlpDecode * don't create bloom inside ApplyTransaction * clean * clean * clean * clean * clean * clean * clean * clean * rename method * print timings * print timings * print timings * sort before flush * fix err lint * clean * move tests to transactions * compressed version * up bound * up bound * more tests * more tests * more tests * more tests * better removal * clean * better performance of get/put methods * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * optimize rpcdaemon * fix test * fix rpcdaemon * fix test * simplify * simplify * fix nil pointer * clean * revert some changes * add some logs * clean * try without optimize * clean * clean * clean * clean * try * move log_index to own stage * move log_index to own stage * integration add log_index stage * integration add log_index stage * clean * clean * print timing * remove duplicates at unwind * extract truncateBitmaps func * try detect * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * clean * add blackList of topics * clean * clean * clean * clean * clean * clean * clean * clean * sharding 1 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 2 * sharded 3 * sharded 3 * sharded 3 * speedup things by putCurrent and putReserve * clean * optimize trim * clean * remove blacklist * add more info to err * ? * clean * clean * clean * clean * clean * working version * switch to cgo version of roaring bitmaps * clean * clean * clean * clean * more docs * clean * clean * fix logs bloom field * Fix debug_getModifiedAccountsByNumber * Try to fix crash * fix problem with "absent block" * fix problem with "absent block" * remove optimize method call * remove roaring iterator * fix problem with rebuild indicess * remove debug prints * tests for eth_getLogs involving topics * add tests for new stage, speparate topics into 2 buckets * version up * remove debug logs * remove debug logs * remove bloom filter implementation * Optimisation * Optimisatin not required, make rpctest lenient to geth errors * Lenient to geth failures Co-authored-by: Alexey Akhunov <akhounov@gmail.com>
2020-09-28 17:18:36 +00:00
"context"
"fmt"
"path/filepath"
"github.com/ledgerwatch/erigon-lib/common"
2022-11-20 03:41:30 +00:00
"github.com/ledgerwatch/erigon-lib/common/datadir"
2021-07-29 11:53:13 +00:00
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
2021-07-29 10:23:23 +00:00
"github.com/ledgerwatch/log/v3"
"github.com/ugorji/go/codec"
)
// migrations apply sequentially in order of this array, skips applied migrations
// it allows - don't worry about merge conflicts and use switch branches
// see also dbutils.Migrations - it stores context in which each transaction was exectured - useful for bug-reports
//
// Idempotency is expected
// Best practices to achieve Idempotency:
2022-08-10 12:04:13 +00:00
// - in dbutils/bucket.go add suffix for existing bucket variable, create new bucket with same variable name.
// Example:
// - SyncStageProgress = []byte("SSP1")
// - SyncStageProgressOld1 = []byte("SSP1")
// - SyncStageProgress = []byte("SSP2")
// - in the beginning of migration: check that old bucket exists, clear new bucket
// - in the end:drop old bucket (not in defer!).
// - if you need migrate multiple buckets - create separate migration for each bucket
// - write test - and check that it's safe to apply same migration twice
var migrations = map[kv.Label][]Migration{
kv.ChainDB: {
dbSchemaVersion5,
2023-05-24 08:52:51 +00:00
TxsBeginEnd,
TxsV3,
ProhibitNewDownloadsLock,
},
kv.TxPoolDB: {},
kv.SentryDB: {},
}
type Callback func(tx kv.RwTx, progress []byte, isDone bool) error
type Migration struct {
Name string
Up func(db kv.RwDB, dirs datadir.Dirs, progress []byte, BeforeCommit Callback, logger log.Logger) error
}
var (
ErrMigrationNonUniqueName = fmt.Errorf("please provide unique migration name")
ErrMigrationCommitNotCalled = fmt.Errorf("migration before-commit function was not called")
ErrMigrationETLFilesDeleted = fmt.Errorf("db migration progress was interrupted after extraction step and ETL files was deleted, please contact development team for help or re-sync from scratch")
)
func NewMigrator(label kv.Label) *Migrator {
return &Migrator{
Migrations: migrations[label],
}
}
type Migrator struct {
Migrations []Migration
}
func AppliedMigrations(tx kv.Tx, withPayload bool) (map[string][]byte, error) {
applied := map[string][]byte{}
err := tx.ForEach(kv.Migrations, nil, func(k []byte, v []byte) error {
if bytes.HasPrefix(k, []byte("_progress_")) {
return nil
}
if withPayload {
applied[string(common.CopyBytes(k))] = common.CopyBytes(v)
} else {
applied[string(common.CopyBytes(k))] = []byte{}
}
return nil
})
return applied, err
}
func (m *Migrator) HasPendingMigrations(db kv.RwDB) (bool, error) {
var has bool
if err := db.View(context.Background(), func(tx kv.Tx) error {
pending, err := m.PendingMigrations(tx)
if err != nil {
return err
}
has = len(pending) > 0
return nil
}); err != nil {
return false, err
}
return has, nil
}
func (m *Migrator) PendingMigrations(tx kv.Tx) ([]Migration, error) {
applied, err := AppliedMigrations(tx, false)
if err != nil {
return nil, err
}
counter := 0
for i := range m.Migrations {
v := m.Migrations[i]
if _, ok := applied[v.Name]; ok {
continue
}
counter++
}
pending := make([]Migration, 0, counter)
for i := range m.Migrations {
v := m.Migrations[i]
if _, ok := applied[v.Name]; ok {
continue
}
pending = append(pending, v)
}
return pending, nil
}
func (m *Migrator) VerifyVersion(db kv.RwDB) error {
if err := db.View(context.Background(), func(tx kv.Tx) error {
major, minor, _, ok, err := rawdb.ReadDBSchemaVersion(tx)
if err != nil {
return fmt.Errorf("reading DB schema version: %w", err)
}
if ok {
if major > kv.DBSchemaVersion.Major {
return fmt.Errorf("cannot downgrade major DB version from %d to %d", major, kv.DBSchemaVersion.Major)
} else if major == kv.DBSchemaVersion.Major {
if minor > kv.DBSchemaVersion.Minor {
return fmt.Errorf("cannot downgrade minor DB version from %d.%d to %d.%d", major, minor, kv.DBSchemaVersion.Major, kv.DBSchemaVersion.Major)
}
} else {
// major < kv.DBSchemaVersion.Major
if kv.DBSchemaVersion.Major-major > 1 {
return fmt.Errorf("cannot upgrade major DB version for more than 1 version from %d to %d, use integration tool if you know what you are doing", major, kv.DBSchemaVersion.Major)
}
}
}
return nil
}); err != nil {
return fmt.Errorf("migrator.VerifyVersion: %w", err)
}
return nil
}
func (m *Migrator) Apply(db kv.RwDB, dataDir string, logger log.Logger) error {
if len(m.Migrations) == 0 {
return nil
}
dirs := datadir.New(dataDir)
var applied map[string][]byte
2022-03-09 09:46:48 +00:00
if err := db.View(context.Background(), func(tx kv.Tx) error {
var err error
applied, err = AppliedMigrations(tx, false)
if err != nil {
return fmt.Errorf("reading applied migrations: %w", err)
}
return nil
}); err != nil {
return err
}
if err := m.VerifyVersion(db); err != nil {
return fmt.Errorf("migrator.Apply: %w", err)
}
// migration names must be unique, protection against people's mistake
uniqueNameCheck := map[string]bool{}
for i := range m.Migrations {
_, ok := uniqueNameCheck[m.Migrations[i].Name]
if ok {
return fmt.Errorf("%w, duplicate: %s", ErrMigrationNonUniqueName, m.Migrations[i].Name)
}
uniqueNameCheck[m.Migrations[i].Name] = true
}
for i := range m.Migrations {
v := m.Migrations[i]
if _, ok := applied[v.Name]; ok {
continue
}
callbackCalled := false // commit function must be called if no error, protection against people's mistake
logger.Info("Apply migration", "name", v.Name)
var progress []byte
if err := db.View(context.Background(), func(tx kv.Tx) (err error) {
progress, err = tx.GetOne(kv.Migrations, []byte("_progress_"+v.Name))
return err
}); err != nil {
return fmt.Errorf("migrator.Apply: %w", err)
}
dirs.Tmp = filepath.Join(dirs.DataDir, "migrations", v.Name)
if err := v.Up(db, dirs, progress, func(tx kv.RwTx, key []byte, isDone bool) error {
if !isDone {
if key != nil {
if err := tx.Put(kv.Migrations, []byte("_progress_"+v.Name), key); err != nil {
return err
}
}
return nil
}
callbackCalled = true
2020-09-08 19:39:43 +00:00
stagesProgress, err := MarshalMigrationPayload(tx)
if err != nil {
return err
}
err = tx.Put(kv.Migrations, []byte(v.Name), stagesProgress)
if err != nil {
return err
}
err = tx.Delete(kv.Migrations, []byte("_progress_"+v.Name))
if err != nil {
return err
}
2020-09-08 19:39:43 +00:00
return nil
}, logger); err != nil {
return fmt.Errorf("migrator.Apply.Up: %s, %w", v.Name, err)
}
if !callbackCalled {
2020-08-12 02:57:55 +00:00
return fmt.Errorf("%w: %s", ErrMigrationCommitNotCalled, v.Name)
}
logger.Info("Applied migration", "name", v.Name)
}
if err := db.Update(context.Background(), func(tx kv.RwTx) error {
return rawdb.WriteDBSchemaVersion(tx)
}); err != nil {
return fmt.Errorf("migrator.Apply: %w", err)
Integration tests 1 (#1793) * Initial commit * Add sentry gRPC interface * p2psentry directory * Update README.md * Update README.md * Update README.md * Add go package * Correct syntax * add external downloader interface (#2) * Add txpool (#3) * Add private API (#4) * Invert control.proto, add PeerMinBlock, Separare incoming Tx message into a separate stream (#5) Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local> * Separate upload messages into its own stream (#6) Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local> * Only send changed accounts to listeners (#7) * Txpool interface doc (#9) * Add architecture diagram source and picture (#10) * Typed hashes (#11) * Typed hashes * Fix PeerId * 64-bit tx nonce * Add proper golang packages, max_block into p2p sentry Status (#12) * Add proper golang packages, max_block into p2p sentry Status * Change EtherReply to address Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local> * Add Rust infrastructure (#13) * DB stats methods removed by https://github.com/ledgerwatch/turbo-geth/pull/1665 * more p2p methods (#15) * add mining methods (#16) * First draft of Consensus gRPC interface (#14) * Update Rust build * Fix interfaces in architecture diagram (#17) * Fix KV interface provider * Fix Consensus interface provider * drop java attributes (#18) * tx pool remove unused import (#19) * ethbackend: add protocol version and client version (#20) * Add missing ethbackend I/F (#21) * Add interface versioning mechanism (#23) Add versioning in KV interface Co-authored-by: Artem Vorotnikov <artem@vorotnikov.me> * spec of tx pool method (#24) * spec of tx pool method (#25) * Update version.proto * Refactor interface versioning * Refactor interface versioning * Testing interface * Remove tree * Fix * Build testing protos * Fix * Fix * Update to the newer interfaces * Add ProtocolVersion and ClientVersion stubs * Hook up ProtocolVersion and ClientVersion * Remove service * Add compatibility checks for RPC daemon * Fix typos * Properly update DB schema version * Fix test * Add test for KV compatibility| * Info messages about compability for RPC daemon * DB schema version to be one key * Update release intructions Co-authored-by: Artem Vorotnikov <artem@vorotnikov.me> Co-authored-by: b00ris <b00ris@mail.ru> Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local> Co-authored-by: lightclient <14004106+lightclient@users.noreply.github.com> Co-authored-by: canepat <16927169+canepat@users.noreply.github.com> Co-authored-by: Alex Sharov <AskAlexSharov@gmail.com> Co-authored-by: canepat <tullio.canepa@gmail.com> Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro.local>
2021-04-24 15:46:29 +00:00
}
logger.Info("Updated DB schema to", "version", fmt.Sprintf("%d.%d.%d", kv.DBSchemaVersion.Major, kv.DBSchemaVersion.Minor, kv.DBSchemaVersion.Patch))
return nil
}
2020-08-04 09:25:28 +00:00
func MarshalMigrationPayload(db kv.Getter) ([]byte, error) {
s := map[string][]byte{}
buf := bytes.NewBuffer(nil)
encoder := codec.NewEncoder(buf, &codec.CborHandle{})
for _, stage := range stages.AllStages {
v, err := db.GetOne(kv.SyncStageProgress, []byte(stage))
if err != nil {
return nil, err
2020-08-04 09:25:28 +00:00
}
if len(v) > 0 {
s[string(stage)] = common.CopyBytes(v)
}
}
if err := encoder.Encode(s); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func UnmarshalMigrationPayload(data []byte) (map[string][]byte, error) {
s := map[string][]byte{}
if err := codec.NewDecoder(bytes.NewReader(data), &codec.CborHandle{}).Decode(&s); err != nil {
return nil, err
}
return s, nil
}