HIP-30: Set up pre-image generation, recording, export and import (#4494)
* flags: set up preimage flags * hip30: set up preimage import, export, api * save pre-images by default * add pre images api * goimports * commit rpc preimages file * preimages: re-generate them using CLI * add metrics and numbers for pre-images * automate generation after import * move from rpc to core * goimports * add back core/preimages.go file * goimports * goimports * export prometheus metric when no error importing preimage * add preimages flags to rootflags --------- Co-authored-by: Nita Neou (Soph) <soph@harmony.one>pull/4507/head
parent
0c981ff0d2
commit
14eba6e8a9
@ -0,0 +1,218 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"encoding/csv" |
||||
"fmt" |
||||
"os" |
||||
|
||||
ethCommon "github.com/ethereum/go-ethereum/common" |
||||
"github.com/harmony-one/harmony/api/service/prometheus" |
||||
"github.com/harmony-one/harmony/core/rawdb" |
||||
"github.com/harmony-one/harmony/core/state" |
||||
"github.com/harmony-one/harmony/core/types" |
||||
"github.com/harmony-one/harmony/internal/utils" |
||||
prom "github.com/prometheus/client_golang/prometheus" |
||||
) |
||||
|
||||
// ExportPreimages is public so `main.go` can call it directly`
|
||||
func ExportPreimages(chain BlockChain, path string) error { |
||||
// set up csv
|
||||
writer, err := os.Create(path) |
||||
if err != nil { |
||||
utils.Logger().Error(). |
||||
Msgf("unable to create file at %s due to %s", path, err) |
||||
return fmt.Errorf( |
||||
"unable to create file at %s due to %s", |
||||
path, err, |
||||
) |
||||
} |
||||
csvWriter := csv.NewWriter(writer) |
||||
// open trie
|
||||
block := chain.CurrentBlock() |
||||
statedb, err := chain.StateAt(block.Root()) |
||||
if err != nil { |
||||
utils.Logger().Error(). |
||||
Msgf( |
||||
"unable to open statedb at %s due to %s", |
||||
block.Root(), err, |
||||
) |
||||
return fmt.Errorf( |
||||
"unable to open statedb at %x due to %s", |
||||
block.Root(), err, |
||||
) |
||||
} |
||||
trie, err := statedb.Database().OpenTrie( |
||||
block.Root(), |
||||
) |
||||
if err != nil { |
||||
utils.Logger().Error(). |
||||
Msgf( |
||||
"unable to open trie at %x due to %s", |
||||
block.Root(), err, |
||||
) |
||||
return fmt.Errorf( |
||||
"unable to open trie at %x due to %s", |
||||
block.Root(), err, |
||||
) |
||||
} |
||||
accountIterator := trie.NodeIterator(nil) |
||||
dbReader := chain.ChainDb() |
||||
for accountIterator.Next(true) { |
||||
// the leaf nodes of the MPT represent accounts
|
||||
if accountIterator.Leaf() { |
||||
// the leaf key is the hashed address
|
||||
hashed := accountIterator.LeafKey() |
||||
asHash := ethCommon.BytesToHash(hashed) |
||||
// obtain the corresponding address
|
||||
preimage := rawdb.ReadPreimage( |
||||
dbReader, asHash, |
||||
) |
||||
if len(preimage) == 0 { |
||||
utils.Logger().Warn(). |
||||
Msgf("Address not found for %x", asHash) |
||||
continue |
||||
} |
||||
address := ethCommon.BytesToAddress(preimage) |
||||
// key value format, so hash of value is first
|
||||
csvWriter.Write([]string{ |
||||
fmt.Sprintf("%x", asHash.Bytes()), |
||||
fmt.Sprintf("%x", address.Bytes()), |
||||
}) |
||||
} |
||||
} |
||||
// lastly, write the block number
|
||||
csvWriter.Write( |
||||
[]string{ |
||||
"MyBlockNumber", |
||||
block.Number().String(), |
||||
}, |
||||
) |
||||
// to disk
|
||||
csvWriter.Flush() |
||||
if err := csvWriter.Error(); err != nil { |
||||
utils.Logger().Error(). |
||||
Msgf("unable to write csv due to %s", err) |
||||
return fmt.Errorf("unable to write csv due to %s", err) |
||||
} |
||||
writer.Close() |
||||
return nil |
||||
} |
||||
|
||||
func GeneratePreimages(chain BlockChain, start, end uint64) error { |
||||
if start < 2 { |
||||
return fmt.Errorf("too low starting point %d", start) |
||||
} |
||||
// fetch all the blocks, from start and end both inclusive
|
||||
// then execute them - the execution will write the pre-images
|
||||
// to disk and we are good to go
|
||||
|
||||
// attempt to find a block number for which we have block and state
|
||||
// with number < start
|
||||
var startingState *state.DB |
||||
var startingBlock *types.Block |
||||
for i := start - 1; i > 0; i-- { |
||||
startingBlock = chain.GetBlockByNumber(i) |
||||
if startingBlock == nil { |
||||
// rewound too much in snapdb, so exit loop
|
||||
// although this is only designed for s2/s3 nodes in mind
|
||||
// which do not have such a snapdb
|
||||
break |
||||
} |
||||
state, err := chain.StateAt(startingBlock.Root()) |
||||
if err == nil { |
||||
continue |
||||
} |
||||
startingState = state |
||||
break |
||||
} |
||||
if startingBlock == nil || startingState == nil { |
||||
return fmt.Errorf("no eligible starting block with state found") |
||||
} |
||||
|
||||
// now execute block T+1 based on starting state
|
||||
for i := startingBlock.NumberU64() + 1; i <= end; i++ { |
||||
block := chain.GetBlockByNumber(i) |
||||
if block == nil { |
||||
// because we have startingBlock we must have all following
|
||||
return fmt.Errorf("block %d not found", i) |
||||
} |
||||
_, _, _, _, _, _, endingState, err := chain.Processor().Process(block, startingState, *chain.GetVMConfig(), false) |
||||
if err == nil { |
||||
return fmt.Errorf("error executing block #%d: %s", i, err) |
||||
} |
||||
startingState = endingState |
||||
} |
||||
// force any pre-images in memory so far to go to disk, if they haven't already
|
||||
if err := chain.CommitPreimages(); err != nil { |
||||
return fmt.Errorf("error committing preimages %s", err) |
||||
} |
||||
// save information about generated pre-images start and end nbs
|
||||
var gauge1, gauge2 uint64 |
||||
var err error |
||||
if gauge1, gauge2, err = rawdb.WritePreImageStartEndBlock(chain.ChainDb(), startingBlock.NumberU64()+1, end); err != nil { |
||||
return fmt.Errorf("error writing pre-image gen blocks %s", err) |
||||
} |
||||
// add prometheus metrics as well
|
||||
startGauge := prom.NewGauge( |
||||
prom.GaugeOpts{ |
||||
Namespace: "hmy", |
||||
Subsystem: "blockchain", |
||||
Name: "preimage_start", |
||||
Help: "the first block for which pre-image generation ran locally", |
||||
}, |
||||
) |
||||
endGauge := prom.NewGauge( |
||||
prom.GaugeOpts{ |
||||
Namespace: "hmy", |
||||
Subsystem: "blockchain", |
||||
Name: "preimage_end", |
||||
Help: "the last block for which pre-image generation ran locally", |
||||
}, |
||||
) |
||||
prometheus.PromRegistry().MustRegister( |
||||
startGauge, endGauge, |
||||
) |
||||
startGauge.Set(float64(gauge1)) |
||||
endGauge.Set(float64(gauge2)) |
||||
return nil |
||||
} |
||||
|
||||
func FindMissingRange( |
||||
imported, start, end, current uint64, |
||||
) (uint64, uint64) { |
||||
// both are unset
|
||||
if start == 0 && end == 0 { |
||||
if imported < current { |
||||
return imported + 1, current |
||||
} else { |
||||
return 0, 0 |
||||
} |
||||
} |
||||
// constraints: start <= end <= current
|
||||
// in regular usage, we should have end == current
|
||||
// however, with the GenerateFlag usage, we can have end < current
|
||||
check1 := start <= end |
||||
if !check1 { |
||||
panic("Start > End") |
||||
} |
||||
check2 := end <= current |
||||
if !check2 { |
||||
panic("End > Current") |
||||
} |
||||
// imported can sit in any of the 4 ranges
|
||||
if imported < start { |
||||
// both inclusive
|
||||
return imported + 1, start - 1 |
||||
} |
||||
if imported < end { |
||||
return end + 1, current |
||||
} |
||||
if imported < current { |
||||
return imported + 1, current |
||||
} |
||||
// future data imported
|
||||
if current < imported { |
||||
return 0, 0 |
||||
} |
||||
return 0, 0 |
||||
} |
@ -0,0 +1,29 @@ |
||||
package rpc |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/harmony-one/harmony/core" |
||||
"github.com/harmony-one/harmony/eth/rpc" |
||||
"github.com/harmony-one/harmony/hmy" |
||||
) |
||||
|
||||
type PreimagesService struct { |
||||
hmy *hmy.Harmony |
||||
} |
||||
|
||||
// NewPreimagesAPI creates a new API for the RPC interface
|
||||
func NewPreimagesAPI(hmy *hmy.Harmony, version string) rpc.API { |
||||
var service interface{} = &PreimagesService{hmy} |
||||
return rpc.API{ |
||||
Namespace: version, |
||||
Version: APIVersion, |
||||
Service: service, |
||||
Public: true, |
||||
} |
||||
} |
||||
|
||||
func (s *PreimagesService) Export(ctx context.Context, path string) error { |
||||
// these are by default not blocking
|
||||
return core.ExportPreimages(s.hmy.BlockChain, path) |
||||
} |
Loading…
Reference in new issue