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
Max 1 year ago committed by GitHub
parent 0c981ff0d2
commit 14eba6e8a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      cmd/harmony/default.go
  2. 64
      cmd/harmony/flags.go
  3. 89
      cmd/harmony/flags_test.go
  4. 122
      cmd/harmony/main.go
  5. 1
      core/blockchain.go
  6. 17
      core/blockchain_impl.go
  7. 4
      core/blockchain_stub.go
  8. 6
      core/epochchain.go
  9. 218
      core/preimages.go
  10. 62
      core/rawdb/accessors_state.go
  11. 4
      core/rawdb/schema.go
  12. 21
      internal/cli/flag.go
  13. 15
      internal/cli/parse.go
  14. 10
      internal/configs/harmony/harmony.go
  15. 1
      internal/configs/node/config.go
  16. 7
      internal/shardchain/shardchains.go
  17. 29
      rpc/preimages.go
  18. 3
      rpc/rpc.go

@ -65,6 +65,7 @@ var defaultConfig = harmonyconfig.HarmonyConfig{
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: nodeconfig.DefaultRPCRateLimit, RequestsPerSecond: nodeconfig.DefaultRPCRateLimit,
EvmCallTimeout: nodeconfig.DefaultEvmCallTimeout, EvmCallTimeout: nodeconfig.DefaultEvmCallTimeout,
PreimagesEnabled: false,
}, },
BLSKeys: harmonyconfig.BlsConfig{ BLSKeys: harmonyconfig.BlsConfig{
KeyDir: "./.hmy/blskeys", KeyDir: "./.hmy/blskeys",
@ -149,6 +150,13 @@ var defaultRevertConfig = harmonyconfig.RevertConfig{
RevertTo: 0, RevertTo: 0,
} }
var defaultPreimageConfig = harmonyconfig.PreimageConfig{
ImportFrom: "",
ExportTo: "",
GenerateStart: 0,
GenerateEnd: 0,
}
var defaultLogContext = harmonyconfig.LogContext{ var defaultLogContext = harmonyconfig.LogContext{
IP: "127.0.0.1", IP: "127.0.0.1",
Port: 9000, Port: 9000,
@ -291,6 +299,11 @@ func getDefaultRevertConfigCopy() harmonyconfig.RevertConfig {
return config return config
} }
func getDefaultPreimageConfigCopy() harmonyconfig.PreimageConfig {
config := defaultPreimageConfig
return config
}
func getDefaultLogContextCopy() harmonyconfig.LogContext { func getDefaultLogContextCopy() harmonyconfig.LogContext {
config := defaultLogContext config := defaultLogContext
return config return config

@ -90,6 +90,7 @@ var (
rpcOptFlags = []cli.Flag{ rpcOptFlags = []cli.Flag{
rpcDebugEnabledFlag, rpcDebugEnabledFlag,
rpcPreimagesEnabledFlag,
rpcEthRPCsEnabledFlag, rpcEthRPCsEnabledFlag,
rpcStakingRPCsEnabledFlag, rpcStakingRPCsEnabledFlag,
rpcLegacyRPCsEnabledFlag, rpcLegacyRPCsEnabledFlag,
@ -205,6 +206,13 @@ var (
revertBeforeFlag, revertBeforeFlag,
} }
preimageFlags = []cli.Flag{
preimageImportFlag,
preimageExportFlag,
preimageGenerateStartFlag,
preimageGenerateEndFlag,
}
legacyRevertFlags = []cli.Flag{ legacyRevertFlags = []cli.Flag{
legacyRevertBeaconFlag, legacyRevertBeaconFlag,
legacyRevertBeforeFlag, legacyRevertBeforeFlag,
@ -370,6 +378,7 @@ func getRootFlags() []cli.Flag {
flags = append(flags, sysFlags...) flags = append(flags, sysFlags...)
flags = append(flags, devnetFlags...) flags = append(flags, devnetFlags...)
flags = append(flags, revertFlags...) flags = append(flags, revertFlags...)
flags = append(flags, preimageFlags...)
flags = append(flags, legacyMiscFlags...) flags = append(flags, legacyMiscFlags...)
flags = append(flags, prometheusFlags...) flags = append(flags, prometheusFlags...)
flags = append(flags, syncFlags...) flags = append(flags, syncFlags...)
@ -827,6 +836,12 @@ var (
DefValue: defaultConfig.RPCOpt.DebugEnabled, DefValue: defaultConfig.RPCOpt.DebugEnabled,
Hidden: true, Hidden: true,
} }
rpcPreimagesEnabledFlag = cli.BoolFlag{
Name: "rpc.preimages",
Usage: "enable preimages export api",
DefValue: defaultConfig.RPCOpt.PreimagesEnabled,
Hidden: true, // not for end users
}
rpcEthRPCsEnabledFlag = cli.BoolFlag{ rpcEthRPCsEnabledFlag = cli.BoolFlag{
Name: "rpc.eth", Name: "rpc.eth",
@ -879,6 +894,9 @@ func applyRPCOptFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
if cli.IsFlagChanged(cmd, rpcDebugEnabledFlag) { if cli.IsFlagChanged(cmd, rpcDebugEnabledFlag) {
config.RPCOpt.DebugEnabled = cli.GetBoolFlagValue(cmd, rpcDebugEnabledFlag) config.RPCOpt.DebugEnabled = cli.GetBoolFlagValue(cmd, rpcDebugEnabledFlag)
} }
if cli.IsFlagChanged(cmd, rpcPreimagesEnabledFlag) {
config.RPCOpt.PreimagesEnabled = cli.GetBoolFlagValue(cmd, rpcPreimagesEnabledFlag)
}
if cli.IsFlagChanged(cmd, rpcEthRPCsEnabledFlag) { if cli.IsFlagChanged(cmd, rpcEthRPCsEnabledFlag) {
config.RPCOpt.EthRPCsEnabled = cli.GetBoolFlagValue(cmd, rpcEthRPCsEnabledFlag) config.RPCOpt.EthRPCsEnabled = cli.GetBoolFlagValue(cmd, rpcEthRPCsEnabledFlag)
} }
@ -1656,6 +1674,52 @@ func applyRevertFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
} }
} }
var (
preimageImportFlag = cli.StringFlag{
Name: "preimage.import",
Usage: "Import pre-images from CSV file",
Hidden: true,
DefValue: defaultPreimageConfig.ImportFrom,
}
preimageExportFlag = cli.StringFlag{
Name: "preimage.export",
Usage: "Export pre-images to CSV file",
Hidden: true,
DefValue: defaultPreimageConfig.ExportTo,
}
preimageGenerateStartFlag = cli.Uint64Flag{
Name: "preimage.start",
Usage: "The block number from which pre-images are to be generated",
Hidden: true,
DefValue: defaultPreimageConfig.GenerateStart,
}
preimageGenerateEndFlag = cli.Uint64Flag{
Name: "preimage.end",
Usage: "The block number upto and including which pre-images are to be generated",
Hidden: true,
DefValue: defaultPreimageConfig.GenerateEnd,
}
)
func applyPreimageFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
if cli.HasFlagsChanged(cmd, preimageFlags) {
cfg := getDefaultPreimageConfigCopy()
config.Preimage = &cfg
}
if cli.IsFlagChanged(cmd, preimageImportFlag) {
config.Preimage.ImportFrom = cli.GetStringFlagValue(cmd, preimageImportFlag)
}
if cli.IsFlagChanged(cmd, preimageExportFlag) {
config.Preimage.ExportTo = cli.GetStringFlagValue(cmd, preimageExportFlag)
}
if cli.IsFlagChanged(cmd, preimageGenerateStartFlag) {
config.Preimage.GenerateStart = cli.GetUint64FlagValue(cmd, preimageGenerateStartFlag)
}
if cli.IsFlagChanged(cmd, preimageGenerateEndFlag) {
config.Preimage.GenerateEnd = cli.GetUint64FlagValue(cmd, preimageGenerateEndFlag)
}
}
var ( var (
legacyPortFlag = cli.IntFlag{ legacyPortFlag = cli.IntFlag{
Name: "port", Name: "port",

@ -92,6 +92,7 @@ func TestHarmonyFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
WS: harmonyconfig.WsConfig{ WS: harmonyconfig.WsConfig{
Enabled: true, Enabled: true,
@ -752,6 +753,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -766,6 +768,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -780,6 +783,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -794,6 +798,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -808,6 +813,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -822,6 +828,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -836,6 +843,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 2000, RequestsPerSecond: 2000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -850,6 +858,7 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: false, RateLimterEnabled: false,
RequestsPerSecond: 2000, RequestsPerSecond: 2000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout, EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
}, },
}, },
@ -864,6 +873,22 @@ func TestRPCOptFlags(t *testing.T) {
RateLimterEnabled: true, RateLimterEnabled: true,
RequestsPerSecond: 1000, RequestsPerSecond: 1000,
EvmCallTimeout: "10s", EvmCallTimeout: "10s",
PreimagesEnabled: defaultConfig.RPCOpt.PreimagesEnabled,
},
},
{
args: []string{"--rpc.preimages"},
expConfig: harmonyconfig.RpcOptConfig{
DebugEnabled: false,
EthRPCsEnabled: true,
StakingRPCsEnabled: true,
LegacyRPCsEnabled: true,
RpcFilterFile: "./.hmy/rpc_filter.txt",
RateLimterEnabled: true,
RequestsPerSecond: 1000,
EvmCallTimeout: defaultConfig.RPCOpt.EvmCallTimeout,
PreimagesEnabled: true,
}, },
}, },
} }
@ -1509,6 +1534,70 @@ func TestRevertFlags(t *testing.T) {
} }
} }
func TestPreimageFlags(t *testing.T) {
tests := []struct {
args []string
expConfig *harmonyconfig.PreimageConfig
expErr error
}{
{
args: []string{},
expConfig: nil,
},
{
args: []string{"--preimage.import", "/path/to/source.csv"},
expConfig: &harmonyconfig.PreimageConfig{
ImportFrom: "/path/to/source.csv",
ExportTo: defaultPreimageConfig.ExportTo,
GenerateStart: defaultPreimageConfig.GenerateStart,
GenerateEnd: defaultPreimageConfig.GenerateEnd,
},
},
{
args: []string{"--preimage.export", "/path/to/destination.csv"},
expConfig: &harmonyconfig.PreimageConfig{
ImportFrom: defaultPreimageConfig.ImportFrom,
ExportTo: "/path/to/destination.csv",
GenerateStart: defaultPreimageConfig.GenerateStart,
GenerateEnd: defaultPreimageConfig.GenerateEnd,
},
},
{
args: []string{"--preimage.start", "1"},
expConfig: &harmonyconfig.PreimageConfig{
ImportFrom: defaultPreimageConfig.ImportFrom,
ExportTo: defaultPreimageConfig.ExportTo,
GenerateStart: 1,
GenerateEnd: defaultPreimageConfig.GenerateEnd,
},
},
{
args: []string{"--preimage.end", "2"},
expConfig: &harmonyconfig.PreimageConfig{
ImportFrom: defaultPreimageConfig.ImportFrom,
ExportTo: defaultPreimageConfig.ExportTo,
GenerateStart: defaultPreimageConfig.GenerateStart,
GenerateEnd: 2,
},
},
}
for i, test := range tests {
ts := newFlagTestSuite(t, preimageFlags, applyPreimageFlags)
hc, err := ts.run(test.args)
if assErr := assertError(err, test.expErr); assErr != nil {
t.Fatalf("Test %v: %v", i, assErr)
}
if err != nil || test.expErr != nil {
continue
}
if !reflect.DeepEqual(hc.Preimage, test.expConfig) {
t.Errorf("Test %v:\n\t%+v\n\t%+v", i, hc.Preimage, test.expConfig)
}
ts.tearDown()
}
}
func TestDNSSyncFlags(t *testing.T) { func TestDNSSyncFlags(t *testing.T) {
tests := []struct { tests := []struct {
args []string args []string

@ -1,7 +1,9 @@
package main package main
import ( import (
"encoding/csv"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"math/rand" "math/rand"
@ -30,6 +32,7 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -45,6 +48,7 @@ import (
"github.com/harmony-one/harmony/common/ntp" "github.com/harmony-one/harmony/common/ntp"
"github.com/harmony-one/harmony/consensus" "github.com/harmony-one/harmony/consensus"
"github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/hmy/downloader" "github.com/harmony-one/harmony/hmy/downloader"
"github.com/harmony-one/harmony/internal/cli" "github.com/harmony-one/harmony/internal/cli"
"github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/common"
@ -60,6 +64,7 @@ import (
"github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/webhooks" "github.com/harmony-one/harmony/webhooks"
prom "github.com/prometheus/client_golang/prometheus"
) )
// Host // Host
@ -246,6 +251,7 @@ func applyRootFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) {
applySysFlags(cmd, config) applySysFlags(cmd, config)
applyDevnetFlags(cmd, config) applyDevnetFlags(cmd, config)
applyRevertFlags(cmd, config) applyRevertFlags(cmd, config)
applyPreimageFlags(cmd, config)
applyPrometheusFlags(cmd, config) applyPrometheusFlags(cmd, config)
applySyncFlags(cmd, config) applySyncFlags(cmd, config)
applyShardDataFlags(cmd, config) applyShardDataFlags(cmd, config)
@ -375,6 +381,122 @@ func setupNodeAndRun(hc harmonyconfig.HarmonyConfig) {
} }
} }
//// code to handle pre-image export, import and generation
if hc.Preimage != nil {
if hc.Preimage.ImportFrom != "" {
reader, err := os.Open(hc.Preimage.ImportFrom)
if err != nil {
fmt.Println("Could not open file for reading", err)
os.Exit(1)
}
csvReader := csv.NewReader(reader)
chain := currentNode.Blockchain()
dbReader := chain.ChainDb()
imported := uint64(0)
for {
record, err := csvReader.Read()
if err == io.EOF {
fmt.Println("MyBlockNumber field missing, cannot proceed")
os.Exit(1)
}
if err != nil {
fmt.Println("Could not read from reader", err)
os.Exit(1)
}
// this means the address is a number
if blockNumber, err := strconv.ParseUint(record[1], 10, 64); err == nil {
if record[0] == "MyBlockNumber" {
// set this value in database, and prometheus, if needed
prev, err := rawdb.ReadPreimageImportBlock(dbReader)
if err != nil {
fmt.Println("No prior value found, overwriting")
}
if blockNumber > prev {
if rawdb.WritePreimageImportBlock(dbReader, blockNumber) != nil {
fmt.Println("Error saving last import block", err)
os.Exit(1)
}
// export blockNumber to prometheus
gauge := prom.NewGauge(
prom.GaugeOpts{
Namespace: "hmy",
Subsystem: "blockchain",
Name: "last_preimage_import",
Help: "the last known block for which preimages were imported",
},
)
prometheus.PromRegistry().MustRegister(
gauge,
)
gauge.Set(float64(blockNumber))
}
// this is the last record
imported = blockNumber
break
}
}
key := ethCommon.HexToHash(record[0])
value := ethCommon.Hex2Bytes(record[1])
// validate
if crypto.Keccak256Hash(value) != key {
fmt.Println("Data mismatch: skipping", record)
continue
}
// add to database
rawdb.WritePreimages(
dbReader, map[ethCommon.Hash][]byte{
key: value,
},
)
}
// now, at this point, we will have to generate missing pre-images
if imported != 0 {
genStart, _ := rawdb.ReadPreImageStartBlock(dbReader)
genEnd, _ := rawdb.ReadPreImageEndBlock(dbReader)
current := chain.CurrentBlock().NumberU64()
toGenStart, toGenEnd := core.FindMissingRange(imported, genStart, genEnd, current)
if toGenStart != 0 && toGenEnd != 0 {
if err := core.GeneratePreimages(
chain, toGenStart, toGenEnd,
); err != nil {
fmt.Println("Error generating", err)
os.Exit(1)
}
}
}
os.Exit(0)
} else if exportPath := hc.Preimage.ExportTo; exportPath != "" {
if err := core.ExportPreimages(
currentNode.Blockchain(),
exportPath,
); err != nil {
fmt.Println("Error exporting", err)
os.Exit(1)
}
os.Exit(0)
// both must be set
} else if hc.Preimage.GenerateStart > 0 && hc.Preimage.GenerateEnd > 0 {
chain := currentNode.Blockchain()
end := hc.Preimage.GenerateEnd
if number := chain.CurrentBlock().NumberU64(); number > end {
fmt.Printf(
"Cropping generate endpoint from %d to %d\n",
number, end,
)
end = number
}
if err := core.GeneratePreimages(
chain,
hc.Preimage.GenerateStart, end,
); err != nil {
fmt.Println("Error generating", err)
os.Exit(1)
}
os.Exit(0)
}
os.Exit(0)
}
startMsg := "==== New Harmony Node ====" startMsg := "==== New Harmony Node ===="
if hc.General.NodeType == nodeTypeExplorer { if hc.General.NodeType == nodeTypeExplorer {
startMsg = "==== New Explorer Node ====" startMsg = "==== New Explorer Node ===="

@ -346,6 +346,7 @@ type BlockChain interface {
) (status WriteStatus, err error) ) (status WriteStatus, err error)
GetLeaderPubKeyFromCoinbase(h *block.Header) (*bls.PublicKeyWrapper, error) GetLeaderPubKeyFromCoinbase(h *block.Header) (*bls.PublicKeyWrapper, error)
CommitPreimages() error
// ========== Only For Tikv Start ========== // ========== Only For Tikv Start ==========

@ -149,6 +149,7 @@ var defaultCacheConfig = &CacheConfig{
TrieTimeLimit: 5 * time.Minute, TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 256, SnapshotLimit: 256,
SnapshotWait: true, SnapshotWait: true,
Preimages: true,
} }
type BlockChainImpl struct { type BlockChainImpl struct {
@ -236,7 +237,7 @@ func NewBlockChainWithOptions(
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
// available in the database. It initialises the default Ethereum validator and // available in the database. It initialises the default Ethereum validator and
// Processor. // Processor. As of Aug-23, this is only used by tests
func NewBlockChain( func NewBlockChain(
db ethdb.Database, stateCache state.Database, beaconChain BlockChain, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, db ethdb.Database, stateCache state.Database, beaconChain BlockChain, cacheConfig *CacheConfig, chainConfig *params.ChainConfig,
engine consensus_engine.Engine, vmConfig vm.Config, engine consensus_engine.Engine, vmConfig vm.Config,
@ -366,6 +367,12 @@ func newBlockChainWithOptions(
return nil, errors.WithMessage(err, "failed to build leader rotation meta") return nil, errors.WithMessage(err, "failed to build leader rotation meta")
} }
if cacheConfig.Preimages {
if _, _, err := rawdb.WritePreImageStartEndBlock(bc.ChainDb(), curHeader.NumberU64()+1, 0); err != nil {
return nil, errors.WithMessage(err, "failed to write pre-image start end blocks")
}
}
// Take ownership of this particular state // Take ownership of this particular state
go bc.update() go bc.update()
return bc, nil return bc, nil
@ -1208,6 +1215,10 @@ func (bc *BlockChainImpl) Stop() {
// Flush the collected preimages to disk // Flush the collected preimages to disk
if err := bc.stateCache.TrieDB().CommitPreimages(); err != nil { if err := bc.stateCache.TrieDB().CommitPreimages(); err != nil {
utils.Logger().Error().Interface("err", err).Msg("Failed to commit trie preimages") utils.Logger().Error().Interface("err", err).Msg("Failed to commit trie preimages")
} else {
if _, _, err := rawdb.WritePreImageStartEndBlock(bc.ChainDb(), 0, bc.CurrentBlock().NumberU64()); err != nil {
utils.Logger().Error().Interface("err", err).Msg("Failed to mark preimages end block")
}
} }
// Ensure all live cached entries be saved into disk, so that we can skip // Ensure all live cached entries be saved into disk, so that we can skip
// cache warmup when node restarts. // cache warmup when node restarts.
@ -3694,6 +3705,10 @@ func (bc *BlockChainImpl) InitTiKV(conf *harmonyconfig.TiKVConfig) {
go bc.tikvCleanCache() go bc.tikvCleanCache()
} }
func (bc *BlockChainImpl) CommitPreimages() error {
return bc.stateCache.TrieDB().CommitPreimages()
}
var ( var (
leveldbErrSpec = "leveldb" leveldbErrSpec = "leveldb"
tooManyOpenFilesErrStr = "Too many open files" tooManyOpenFilesErrStr = "Too many open files"

@ -439,3 +439,7 @@ func (a Stub) InitTiKV(conf *harmonyconfig.TiKVConfig) {
func (a Stub) LeaderRotationMeta() (publicKeyBytes []byte, epoch, count, shifts uint64, err error) { func (a Stub) LeaderRotationMeta() (publicKeyBytes []byte, epoch, count, shifts uint64, err error) {
return nil, 0, 0, 0, errors.Errorf("method LeaderRotationMeta not implemented for %s", a.Name) return nil, 0, 0, 0, errors.Errorf("method LeaderRotationMeta not implemented for %s", a.Name)
} }
func (a Stub) CommitPreimages() error {
return errors.Errorf("method CommitPreimages not implemented for %s", a.Name)
}

@ -323,3 +323,9 @@ func (bc *EpochChain) IsSameLeaderAsPreviousBlock(block *types.Block) bool {
func (bc *EpochChain) GetVMConfig() *vm.Config { func (bc *EpochChain) GetVMConfig() *vm.Config {
return bc.vmConfig return bc.vmConfig
} }
func (bc *EpochChain) CommitPreimages() error {
// epoch chain just has last block, which does not have any txs
// so no pre-images here
return nil
}

@ -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
}

@ -147,3 +147,65 @@ func DeleteValidatorCode(db ethdb.KeyValueWriter, hash common.Hash) {
utils.Logger().Error().Err(err).Msg("Failed to delete validator code") utils.Logger().Error().Err(err).Msg("Failed to delete validator code")
} }
} }
func WritePreimageImportBlock(db ethdb.KeyValueWriter, number uint64) error {
return db.Put(preImageImportKey, encodeBlockNumber(number))
}
func ReadPreimageImportBlock(db ethdb.KeyValueReader) (uint64, error) {
val, err := db.Get(preImageImportKey)
if err != nil {
return 0, err
}
return decodeBlockNumber(val), nil
}
func WritePreImageStartEndBlock(
db ethdb.KeyValueStore,
start uint64,
end uint64,
) (
uint64,
uint64,
error,
) {
returnStart := start
returnEnd := end
if start != 0 {
existingStart, err := ReadPreImageStartBlock(db)
if err != nil || existingStart > start {
if err := db.Put(preImageGenStartKey, encodeBlockNumber(start)); err != nil {
return 0, 0, err
} else {
returnStart = existingStart
}
}
}
if end != 0 {
existingEnd, err := ReadPreImageEndBlock(db)
if err != nil || existingEnd < end {
if err := db.Put(preImageGenEndKey, encodeBlockNumber(end)); err != nil {
return 0, 0, err
} else {
returnEnd = existingEnd
}
}
}
return returnStart, returnEnd, nil
}
func ReadPreImageStartBlock(db ethdb.KeyValueReader) (uint64, error) {
val, err := db.Get(preImageGenStartKey)
if err != nil {
return 0, err
}
return decodeBlockNumber(val), nil
}
func ReadPreImageEndBlock(db ethdb.KeyValueReader) (uint64, error) {
val, err := db.Get(preImageGenEndKey)
if err != nil {
return 0, err
}
return decodeBlockNumber(val), nil
}

@ -148,6 +148,10 @@ var (
BloomTrieIndexPrefix = []byte("bltIndex-") BloomTrieIndexPrefix = []byte("bltIndex-")
CliqueSnapshotPrefix = []byte("clique-") CliqueSnapshotPrefix = []byte("clique-")
preImageImportKey = []byte("preimage-import")
preImageGenStartKey = []byte("preimage-gen-start")
preImageGenEndKey = []byte("preimage-gen-end")
) )
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary // LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary

@ -71,8 +71,7 @@ type Int64Flag struct {
Usage string Usage string
Deprecated string Deprecated string
Hidden bool Hidden bool
DefValue int64
DefValue int64
} }
// RegisterTo register the int flag to FlagSet // RegisterTo register the int flag to FlagSet
@ -81,6 +80,22 @@ func (f Int64Flag) RegisterTo(fs *pflag.FlagSet) error {
return markHiddenOrDeprecated(fs, f.Name, f.Deprecated, f.Hidden) return markHiddenOrDeprecated(fs, f.Name, f.Deprecated, f.Hidden)
} }
// Uint64Flag is the flag with uint64 value, used for block number configurations
type Uint64Flag struct {
Name string
Shorthand string
Usage string
Deprecated string
Hidden bool
DefValue uint64
}
// RegisterTo register the int flag to FlagSet
func (f Uint64Flag) RegisterTo(fs *pflag.FlagSet) error {
fs.Uint64P(f.Name, f.Shorthand, f.DefValue, f.Usage)
return markHiddenOrDeprecated(fs, f.Name, f.Deprecated, f.Hidden)
}
// StringSliceFlag is the flag with string slice value // StringSliceFlag is the flag with string slice value
type StringSliceFlag struct { type StringSliceFlag struct {
Name string Name string
@ -143,6 +158,8 @@ func getFlagName(flag Flag) string {
return f.Name return f.Name
case Int64Flag: case Int64Flag:
return f.Name return f.Name
case Uint64Flag:
return f.Name
} }
return "" return ""
} }

@ -76,6 +76,12 @@ func GetInt64FlagValue(cmd *cobra.Command, flag Int64Flag) int64 {
return getInt64FlagValue(cmd.Flags(), flag) return getInt64FlagValue(cmd.Flags(), flag)
} }
// GetInt64FlagValue get the int value for the given Int64Flag from the local flags of the
// cobra command.
func GetUint64FlagValue(cmd *cobra.Command, flag Uint64Flag) uint64 {
return getUint64FlagValue(cmd.Flags(), flag)
}
// GetIntPersistentFlagValue get the int value for the given IntFlag from the persistent // GetIntPersistentFlagValue get the int value for the given IntFlag from the persistent
// flags of the cobra command. // flags of the cobra command.
func GetIntPersistentFlagValue(cmd *cobra.Command, flag IntFlag) int { func GetIntPersistentFlagValue(cmd *cobra.Command, flag IntFlag) int {
@ -100,6 +106,15 @@ func getInt64FlagValue(fs *pflag.FlagSet, flag Int64Flag) int64 {
return val return val
} }
func getUint64FlagValue(fs *pflag.FlagSet, flag Uint64Flag) uint64 {
val, err := fs.GetUint64(flag.Name)
if err != nil {
handleParseError(err)
return 0
}
return val
}
// GetStringSliceFlagValue get the string slice value for the given StringSliceFlag from // GetStringSliceFlagValue get the string slice value for the given StringSliceFlag from
// the local flags of the cobra command. // the local flags of the cobra command.
func GetStringSliceFlagValue(cmd *cobra.Command, flag StringSliceFlag) []string { func GetStringSliceFlagValue(cmd *cobra.Command, flag StringSliceFlag) []string {

@ -36,6 +36,7 @@ type HarmonyConfig struct {
DNSSync DnsSync DNSSync DnsSync
ShardData ShardDataConfig ShardData ShardDataConfig
GPO GasPriceOracleConfig GPO GasPriceOracleConfig
Preimage *PreimageConfig
} }
func (hc HarmonyConfig) ToRPCServerConfig() nodeconfig.RPCServerConfig { func (hc HarmonyConfig) ToRPCServerConfig() nodeconfig.RPCServerConfig {
@ -84,6 +85,7 @@ func (hc HarmonyConfig) ToRPCServerConfig() nodeconfig.RPCServerConfig {
WSPort: hc.WS.Port, WSPort: hc.WS.Port,
WSAuthPort: hc.WS.AuthPort, WSAuthPort: hc.WS.AuthPort,
DebugEnabled: hc.RPCOpt.DebugEnabled, DebugEnabled: hc.RPCOpt.DebugEnabled,
PreimagesEnabled: hc.RPCOpt.PreimagesEnabled,
EthRPCsEnabled: hc.RPCOpt.EthRPCsEnabled, EthRPCsEnabled: hc.RPCOpt.EthRPCsEnabled,
StakingRPCsEnabled: hc.RPCOpt.StakingRPCsEnabled, StakingRPCsEnabled: hc.RPCOpt.StakingRPCsEnabled,
LegacyRPCsEnabled: hc.RPCOpt.LegacyRPCsEnabled, LegacyRPCsEnabled: hc.RPCOpt.LegacyRPCsEnabled,
@ -287,6 +289,7 @@ type RpcOptConfig struct {
RateLimterEnabled bool // Enable Rate limiter for RPC RateLimterEnabled bool // Enable Rate limiter for RPC
RequestsPerSecond int // for RPC rate limiter RequestsPerSecond int // for RPC rate limiter
EvmCallTimeout string // Timeout for eth_call EvmCallTimeout string // Timeout for eth_call
PreimagesEnabled bool // Expose preimage API
} }
type DevnetConfig struct { type DevnetConfig struct {
@ -303,6 +306,13 @@ type RevertConfig struct {
RevertBefore int RevertBefore int
} }
type PreimageConfig struct {
ImportFrom string
ExportTo string
GenerateStart uint64
GenerateEnd uint64
}
type LegacyConfig struct { type LegacyConfig struct {
WebHookConfig *string `toml:",omitempty"` WebHookConfig *string `toml:",omitempty"`
TPBroadcastInvalidTxn *bool `toml:",omitempty"` TPBroadcastInvalidTxn *bool `toml:",omitempty"`

@ -153,6 +153,7 @@ type RPCServerConfig struct {
DebugEnabled bool DebugEnabled bool
PreimagesEnabled bool
EthRPCsEnabled bool EthRPCsEnabled bool
StakingRPCsEnabled bool StakingRPCsEnabled bool
LegacyRPCsEnabled bool LegacyRPCsEnabled bool

@ -100,8 +100,12 @@ func (sc *CollectionImpl) ShardChain(shardID uint32, options ...core.Options) (c
} }
} }
var cacheConfig *core.CacheConfig var cacheConfig *core.CacheConfig
// archival node
if sc.disableCache[shardID] { if sc.disableCache[shardID] {
cacheConfig = &core.CacheConfig{Disabled: true} cacheConfig = &core.CacheConfig{
Disabled: true,
Preimages: true,
}
utils.Logger().Info(). utils.Logger().Info().
Uint32("shardID", shardID). Uint32("shardID", shardID).
Msg("disable cache, running in archival mode") Msg("disable cache, running in archival mode")
@ -110,6 +114,7 @@ func (sc *CollectionImpl) ShardChain(shardID uint32, options ...core.Options) (c
TrieNodeLimit: 256, TrieNodeLimit: 256,
TrieTimeLimit: 2 * time.Minute, TrieTimeLimit: 2 * time.Minute,
TriesInMemory: 128, TriesInMemory: 128,
Preimages: true,
} }
if sc.harmonyconfig != nil { if sc.harmonyconfig != nil {
cacheConfig.TriesInMemory = uint64(sc.harmonyconfig.General.TriesInMemory) cacheConfig.TriesInMemory = uint64(sc.harmonyconfig.General.TriesInMemory)

@ -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)
}

@ -71,6 +71,9 @@ func (n Version) Namespace() string {
func StartServers(hmy *hmy.Harmony, apis []rpc.API, config nodeconfig.RPCServerConfig, rpcOpt harmony.RpcOptConfig) error { func StartServers(hmy *hmy.Harmony, apis []rpc.API, config nodeconfig.RPCServerConfig, rpcOpt harmony.RpcOptConfig) error {
apis = append(apis, getAPIs(hmy, config)...) apis = append(apis, getAPIs(hmy, config)...)
authApis := append(apis, getAuthAPIs(hmy, config.DebugEnabled, config.RateLimiterEnabled, config.RequestsPerSecond)...) authApis := append(apis, getAuthAPIs(hmy, config.DebugEnabled, config.RateLimiterEnabled, config.RequestsPerSecond)...)
if rpcOpt.PreimagesEnabled {
authApis = append(authApis, NewPreimagesAPI(hmy, "preimages"))
}
// load method filter from file (if exist) // load method filter from file (if exist)
var rmf rpc.RpcMethodFilter var rmf rpc.RpcMethodFilter
rpcFilterFilePath := strings.TrimSpace(rpcOpt.RpcFilterFile) rpcFilterFilePath := strings.TrimSpace(rpcOpt.RpcFilterFile)

Loading…
Cancel
Save