HIP-30: Balance migration (#4499)

* 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

* HIP-30: sharding configuration boilerplate

* update comments

* goimports

* HIP-30: minimum validator commission of 7%

Based on #4495, which must be merged before this PR. This PR should be
rebased with dev after #4495 is merged to retain atomicity of changes by
pull request.

* goimports

* HIP-30: Emission split implementation

Note that the allocated split of the emission goes directly to the
recipient (and not via the Reward). This is because rewards are indexed
by validator and not by delegator, and the recipient may/may not have
any delegations which we can reward. Even if one was guaranteed to
exist, it would mess up the math of the validator.

* set up mainnet recipient of emission split

* HIP-30: Emission split addresses for non mainnet

* HIP-30: deactivate shard 2 and 3 validators

* goimports

* update test

* goimports

* migrate balance uring epoch T - 1

highly untested code. also missing is the ability to generate a
pre-migration report for future verification.

* update test

* export prometheus metric when no error importing preimage

* add comment

* test account migration in localnet

* add preimages flags to rootflags

* enable preimages on the whitelist

* add the generate method

* fix cropping log

* fix cropping log

* cropping startpoint when bigger than endpoint

* add support for the rpcblocknumer type

* enable import api

* use earlies block

* debug logs

* debug logs

* debug logs

* debug logs

* fix error catching

* fix error catching

* make end optional for the comand line

* fix cropping logic

* improve error when apply message fails

* add balance on the error

* fix importing

* remove unused imports

---------

Co-authored-by: Nita Neou (Soph) <soph@harmony.one>
Co-authored-by: Soph <35721420+sophoah@users.noreply.github.com>
Co-authored-by: Diego Nava <diego.nava77@hotmail.com>
Co-authored-by: Diego Nava <8563843+diego1q2w@users.noreply.github.com>
pull/4507/head
Max 1 year ago committed by GitHub
parent 115e434f73
commit 688b9335da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 104
      cmd/harmony/main.go
  2. 97
      core/preimages.go
  3. 246
      core/state_processor.go
  4. 2
      internal/params/config.go
  5. 2
      internal/params/protocol_params.go
  6. 33
      node/node.go
  7. 8
      node/node_newblock.go
  8. 32
      node/worker/worker.go
  9. 58
      rpc/preimages.go
  10. 2
      rpc/rpc.go

@ -1,9 +1,7 @@
package main
import (
"encoding/csv"
"fmt"
"io"
"io/ioutil"
"math/big"
"math/rand"
@ -32,7 +30,6 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -48,7 +45,6 @@ import (
"github.com/harmony-one/harmony/common/ntp"
"github.com/harmony-one/harmony/consensus"
"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/internal/cli"
"github.com/harmony-one/harmony/internal/common"
@ -64,7 +60,6 @@ import (
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/webhooks"
prom "github.com/prometheus/client_golang/prometheus"
)
// Host
@ -384,86 +379,13 @@ 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)
if err := core.ImportPreimages(
currentNode.Blockchain(),
hc.Preimage.ImportFrom,
); err != nil {
fmt.Println("Error importing", 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(
@ -475,16 +397,23 @@ func setupNodeAndRun(hc harmonyconfig.HarmonyConfig) {
}
os.Exit(0)
// both must be set
} else if hc.Preimage.GenerateStart > 0 && hc.Preimage.GenerateEnd > 0 {
} else if hc.Preimage.GenerateStart > 0 {
chain := currentNode.Blockchain()
end := hc.Preimage.GenerateEnd
if number := chain.CurrentBlock().NumberU64(); number > end {
current := chain.CurrentBlock().NumberU64()
if end > current {
fmt.Printf(
"Cropping generate endpoint from %d to %d\n",
number, end,
end, current,
)
end = number
end = current
}
if end == 0 {
end = current
}
fmt.Println("Starting generation")
if err := core.GeneratePreimages(
chain,
hc.Preimage.GenerateStart, end,
@ -492,6 +421,7 @@ func setupNodeAndRun(hc harmonyconfig.HarmonyConfig) {
fmt.Println("Error generating", err)
os.Exit(1)
}
fmt.Println("Generation successful")
os.Exit(0)
}
os.Exit(0)

@ -3,9 +3,12 @@ package core
import (
"encoding/csv"
"fmt"
"io"
"os"
"strconv"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/harmony-one/harmony/api/service/prometheus"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state"
@ -14,6 +17,86 @@ import (
prom "github.com/prometheus/client_golang/prometheus"
)
// ImportPreimages is public so `main.go` can call it directly`
func ImportPreimages(chain BlockChain, path string) error {
reader, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open file for reading: %s", err)
}
csvReader := csv.NewReader(reader)
dbReader := chain.ChainDb()
imported := uint64(0)
for {
record, err := csvReader.Read()
if err == io.EOF {
return fmt.Errorf("MyBlockNumber field missing, cannot proceed")
}
if err != nil {
return fmt.Errorf("could not read from reader: %s", err)
}
// 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 {
return fmt.Errorf("no prior value found, overwriting: %s", err)
}
if blockNumber > prev {
if rawdb.WritePreimageImportBlock(dbReader, blockNumber) != nil {
return fmt.Errorf("error saving last import block: %s", err)
}
// 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 := FindMissingRange(imported, genStart, genEnd, current)
if toGenStart != 0 && toGenEnd != 0 {
if err := GeneratePreimages(
chain, toGenStart, toGenEnd,
); err != nil {
return fmt.Errorf("error generating: %s", err)
}
}
}
return nil
}
// ExportPreimages is public so `main.go` can call it directly`
func ExportPreimages(chain BlockChain, path string) error {
// set up csv
@ -102,6 +185,8 @@ func GeneratePreimages(chain BlockChain, start, end uint64) error {
if start < 2 {
return fmt.Errorf("too low starting point %d", start)
}
fmt.Println("generating from", start, "to", end)
// 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
@ -111,15 +196,18 @@ func GeneratePreimages(chain BlockChain, start, end uint64) error {
var startingState *state.DB
var startingBlock *types.Block
for i := start - 1; i > 0; i-- {
fmt.Println("finding block number", i)
startingBlock = chain.GetBlockByNumber(i)
if startingBlock == nil {
fmt.Println("not found block number", i)
// 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
}
fmt.Println("found block number", startingBlock.NumberU64(), startingBlock.Root().Hex())
state, err := chain.StateAt(startingBlock.Root())
if err == nil {
if err != nil {
continue
}
startingState = state
@ -131,18 +219,23 @@ func GeneratePreimages(chain BlockChain, start, end uint64) error {
// now execute block T+1 based on starting state
for i := startingBlock.NumberU64() + 1; i <= end; i++ {
if i%100000 == 0 {
fmt.Println("processing block", 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 {
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
fmt.Println("committing images")
if err := chain.CommitPreimages(); err != nil {
return fmt.Errorf("error committing preimages %s", err)
}

@ -17,6 +17,8 @@
package core
import (
"encoding/binary"
"fmt"
"math/big"
"time"
@ -28,6 +30,7 @@ import (
"github.com/harmony-one/harmony/block"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/consensus/reward"
"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/core/vm"
@ -41,6 +44,11 @@ import (
"github.com/pkg/errors"
)
var (
ErrNoMigrationRequired = errors.New("No balance migration required")
ErrNoMigrationPossible = errors.New("No balance migration possible")
)
const (
resultCacheLimit = 64 // The number of cached results from processing blocks
)
@ -128,43 +136,63 @@ func (p *StateProcessor) Process(
return nil, nil, nil, nil, 0, nil, statedb, err
}
startTime := time.Now()
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i)
receipt, cxReceipt, stakeMsgs, _, err := ApplyTransaction(
p.config, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
)
if err != nil {
processTxsAndStxs := true
cxReceipt, err := MayBalanceMigration(
gp, header, statedb, p.bc, p.config,
)
if err != nil {
if err == ErrNoMigrationPossible {
// ran out of accounts
processTxsAndStxs = false
}
if err != ErrNoMigrationRequired {
return nil, nil, nil, nil, 0, nil, statedb, err
}
receipts = append(receipts, receipt)
} else {
if cxReceipt != nil {
outcxs = append(outcxs, cxReceipt)
// only 1 cx per block
processTxsAndStxs = false
}
if len(stakeMsgs) > 0 {
blockStakeMsgs = append(blockStakeMsgs, stakeMsgs...)
}
allLogs = append(allLogs, receipt.Logs...)
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Process Normal Txns")
startTime = time.Now()
// Iterate over and process the staking transactions
L := len(block.Transactions())
for i, tx := range block.StakingTransactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i+L)
receipt, _, err := ApplyStakingTransaction(
p.config, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
)
if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err
if processTxsAndStxs {
startTime := time.Now()
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i)
receipt, cxReceipt, stakeMsgs, _, err := ApplyTransaction(
p.config, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
)
if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err
}
receipts = append(receipts, receipt)
if cxReceipt != nil {
outcxs = append(outcxs, cxReceipt)
}
if len(stakeMsgs) > 0 {
blockStakeMsgs = append(blockStakeMsgs, stakeMsgs...)
}
allLogs = append(allLogs, receipt.Logs...)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Process Normal Txns")
startTime = time.Now()
// Iterate over and process the staking transactions
L := len(block.Transactions())
for i, tx := range block.StakingTransactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i+L)
receipt, _, err := ApplyStakingTransaction(
p.config, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
)
if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Process Staking Txns")
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Process Staking Txns")
// incomingReceipts should always be processed
// after transactions (to be consistent with the block proposal)
for _, cx := range block.IncomingReceipts() {
@ -284,7 +312,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// Apply the transaction to the current state (included in the env)
result, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, nil, nil, 0, err
return nil, nil, nil, 0, fmt.Errorf("apply failed from='%s' to='%s' balance='%s': %w", msg.From().Hex(), msg.To().Hex(), statedb.GetBalance(msg.From()).String(), err)
}
// Update the state with pending changes
var root []byte
@ -500,3 +528,161 @@ func MayShardReduction(bc ChainContext, statedb *state.DB, header *block.Header)
statedb.IntermediateRoot(bc.Config().IsS3(header.Epoch()))
return nil
}
func MayBalanceMigration(
gasPool *GasPool,
header *block.Header,
db *state.DB,
chain BlockChain,
config *params.ChainConfig,
) (*types.CXReceipt, error) {
isMainnet := nodeconfig.GetDefaultConfig().GetNetworkType() == nodeconfig.Mainnet
if isMainnet {
if config.IsEpochBeforeHIP30(header.Epoch()) {
nxtShards := shard.Schedule.InstanceForEpoch(
new(big.Int).Add(header.Epoch(), common.Big1),
).NumShards()
if myShard := chain.ShardID(); myShard >= nxtShards {
// i need to send my balances to the destination shard
// however, i do not know when the next epoch will begin
// because only shard 0 can govern that
// so i will just generate one cross shard transaction
// in each block of the epoch. this epoch is defined by
// nxtShards = 2 and curShards = 4
parentRoot := chain.GetBlockByHash(
header.ParentHash(),
).Root() // for examining MPT at this root, should exist
cx, err := generateOneMigrationMessage(
db, parentRoot,
header.NumberU64(),
myShard, uint32(1), // dstShard is always 1
)
if err != nil {
return nil, err
}
if cx != nil {
gasPool.SubGas(params.TxGasXShard)
return cx, nil
}
// both err and cx are nil, which means we
// ran out of eligible accounts in MPT
return nil, ErrNoMigrationPossible
}
}
}
// for testing balance migration on devnet
isDevnet := nodeconfig.GetDefaultConfig().GetNetworkType() == nodeconfig.Devnet
isLocalnet := nodeconfig.GetDefaultConfig().GetNetworkType() == nodeconfig.Localnet
if isDevnet || isLocalnet {
if config.IsEpochBeforeHIP30(header.Epoch()) {
if myShard := chain.ShardID(); myShard != shard.BeaconChainShardID {
parentRoot := chain.GetBlockByHash(
header.ParentHash(),
).Root() // for examining MPT at this root, should exist
cx, err := generateOneMigrationMessage(
db, parentRoot,
header.NumberU64(),
myShard, shard.BeaconChainShardID, // dstShard
)
if err != nil {
return nil, err
}
if cx != nil {
gasPool.SubGas(params.TxGasXShard)
return cx, nil
}
return nil, ErrNoMigrationPossible
}
}
}
return nil, ErrNoMigrationRequired
}
func generateOneMigrationMessage(
statedb *state.DB,
parentRoot common.Hash,
number uint64,
myShard uint32,
dstShard uint32,
) (*types.CXReceipt, error) {
// set up txHash prefix
txHash := make([]byte,
// 8 for uint64 block number
// 4 for uint32 shard id
8+4,
)
binary.LittleEndian.PutUint64(txHash[:8], number)
binary.LittleEndian.PutUint32(txHash[8:], myShard)
// open the trie, as of previous block.
// in this block we aren't processing transactions anyway.
trie, err := statedb.Database().OpenTrie(
parentRoot,
)
if err != nil {
return nil, err
}
// disk db, for use by rawdb
// this is same as blockchain.ChainDb
db := statedb.Database().DiskDB()
// start the iteration
accountIterator := trie.NodeIterator(nil)
// TODO: cache this iteration?
for accountIterator.Next(true) {
// leaf means leaf node of the MPT, which is an account
// the leaf key is the address
if accountIterator.Leaf() {
key := accountIterator.LeafKey()
preimage := rawdb.ReadPreimage(db, common.BytesToHash(key))
if len(preimage) == 0 {
return nil, errors.New(
fmt.Sprintf(
"cannot find preimage for %x", key,
),
)
}
address := common.BytesToAddress(preimage)
// skip blank address
if address == (common.Address{}) {
continue
}
// deserialize
var account state.Account
if err = rlp.DecodeBytes(accountIterator.LeafBlob(), &account); err != nil {
return nil, err
}
// skip contracts
if common.BytesToHash(account.CodeHash) != state.EmptyCodeHash {
continue
}
// skip anything with storage
if account.Root != state.EmptyRootHash {
continue
}
// skip no (or negative?) balance
if account.Balance.Cmp(common.Big0) <= 0 {
continue
}
// for safety, fetch the latest balance (again)
balance := statedb.GetBalance(address)
if balance.Cmp(common.Big0) <= 0 {
continue
}
// adds a journal entry (dirtied)
statedb.SubBalance(address, balance)
// create the receipt
res := &types.CXReceipt{
From: address,
To: &address,
ShardID: myShard,
ToShardID: dstShard,
Amount: balance,
TxHash: common.BytesToHash(txHash),
}
// move from dirty to pending, same as b/w 2 txs
statedb.Finalise(true)
return res, nil
}
}
return nil, nil
}

@ -285,7 +285,7 @@ var (
LeaderRotationExternalBeaconLeaders: big.NewInt(6),
FeeCollectEpoch: big.NewInt(2),
ValidatorCodeFixEpoch: big.NewInt(2),
HIP30Epoch: EpochTBD,
HIP30Epoch: big.NewInt(3),
}
// AllProtocolChanges ...

@ -24,6 +24,8 @@ const (
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
// TxGas ...
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
// TxGasXShard
TxGasXShard uint64 = 23000 // Approximate cost for transferring native tokens across shards. Used in balance migration
// TxGasContractCreation ...
TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions.
// TxGasValidatorCreation ...

@ -255,21 +255,38 @@ func (node *Node) tryBroadcastStaking(stakingTx *staking.StakingTransaction) {
// Add new transactions to the pending transaction list.
func addPendingTransactions(registry *registry.Registry, newTxs types.Transactions) []error {
var (
errs []error
bc = registry.GetBlockchain()
txPool = registry.GetTxPool()
poolTxs = types.PoolTransactions{}
acceptCx = bc.Config().AcceptsCrossTx(bc.CurrentHeader().Epoch())
errs []error
bc = registry.GetBlockchain()
txPool = registry.GetTxPool()
poolTxs = types.PoolTransactions{}
epoch = bc.CurrentHeader().Epoch()
acceptCx = bc.Config().AcceptsCrossTx(epoch)
isBeforeHIP30 = bc.Config().IsEpochBeforeHIP30(epoch)
nxtShards = shard.Schedule.InstanceForEpoch(new(big.Int).Add(epoch, common.Big1)).NumShards()
)
for _, tx := range newTxs {
if tx.ShardID() != tx.ToShardID() && !acceptCx {
errs = append(errs, errors.WithMessage(errInvalidEpoch, "cross-shard tx not accepted yet"))
continue
if tx.ShardID() != tx.ToShardID() {
if !acceptCx {
errs = append(errs, errors.WithMessage(errInvalidEpoch, "cross-shard tx not accepted yet"))
continue
}
if isBeforeHIP30 {
if tx.ToShardID() >= nxtShards {
errs = append(errs, errors.New("shards 2 and 3 are shutting down in the next epoch"))
continue
}
}
}
if tx.IsEthCompatible() && !bc.Config().IsEthCompatible(bc.CurrentBlock().Epoch()) {
errs = append(errs, errors.WithMessage(errInvalidEpoch, "ethereum tx not accepted yet"))
continue
}
if isBeforeHIP30 {
if bc.ShardID() >= nxtShards {
errs = append(errs, errors.New("shards 2 and 3 are shutting down in the next epoch"))
continue
}
}
poolTxs = append(poolTxs, tx)
}
errs = append(errs, registry.GetTxPool().AddRemotes(poolTxs)...)

@ -200,7 +200,13 @@ func (node *Node) ProposeNewBlock(commitSigs chan []byte) (*types.Block, error)
utils.AnalysisEnd("proposeNewBlockChooseFromTxnPool")
}
// Prepare cross shard transaction receipts
// Prepare incoming cross shard transaction receipts
// These are accepted even during the epoch before hip-30
// because the destination shard only receives them after
// balance is deducted on source shard. to prevent this from
// being a significant problem, the source shards will stop
// accepting txs destined to the shards which are shutting down
// one epoch prior the shut down
receiptsList := node.proposeReceiptsProof()
if len(receiptsList) != 0 {
if err := node.Worker.CommitReceipts(receiptsList); err != nil {

@ -150,6 +150,38 @@ func (w *Worker) CommitTransactions(
w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit())
}
// if this is epoch for balance migration, no txs (or stxs)
// will be included in the block
// it is technically feasible for some to end up in the pool
// say, from the last epoch, but those will not be executed
// and no balance will be lost
// any cross-shard transfers destined to a shard being shut down
// will execute (since they are already spent on the source shard)
// but the balance will immediately be returned to shard 1
cx, err := core.MayBalanceMigration(
w.current.gasPool,
w.beacon.CurrentHeader(),
w.current.state,
w.chain,
w.chain.Config(),
)
if err != nil {
if err == core.ErrNoMigrationPossible {
// means we do not accept transactions from the network
return nil
}
if err != core.ErrNoMigrationRequired {
// this shard not migrating => ErrNoMigrationRequired
// any other error means exit this block
return err
}
} else {
if cx != nil {
w.current.outcxs = append(w.current.outcxs, cx)
return nil
}
}
// HARMONY TXNS
normalTxns := types.NewTransactionsByPriceAndNonce(w.current.signer, w.current.ethSigner, pendingNormal)

@ -2,6 +2,7 @@ package rpc
import (
"context"
"fmt"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/eth/rpc"
@ -23,7 +24,62 @@ func NewPreimagesAPI(hmy *hmy.Harmony, version string) rpc.API {
}
}
func (s *PreimagesService) Export(ctx context.Context, path string) error {
func (s *PreimagesService) Export(_ context.Context, path string) error {
// these are by default not blocking
return core.ExportPreimages(s.hmy.BlockChain, path)
}
func (s *PreimagesService) Import(_ context.Context, path string) error {
// these are by default not blocking
return core.ImportPreimages(s.hmy.BlockChain, path)
}
func (s *PreimagesService) Generate(_ context.Context, start, end rpc.BlockNumber) error {
// earliestBlock: the number of blocks in the past where you can generate the preimage from the last block
earliestBlock := uint64(2)
currentBlockNum := s.hmy.CurrentBlock().NumberU64()
var startBlock uint64
switch start {
case rpc.EarliestBlockNumber:
startBlock = earliestBlock
case rpc.LatestBlockNumber:
startBlock = earliestBlock
case rpc.PendingBlockNumber:
startBlock = earliestBlock
default:
startBlock = uint64(start)
}
var endBlock = uint64(end)
switch end {
case rpc.EarliestBlockNumber:
endBlock = currentBlockNum
case rpc.LatestBlockNumber:
endBlock = currentBlockNum
case rpc.PendingBlockNumber:
endBlock = currentBlockNum
default:
endBlock = uint64(end)
}
fmt.Printf("Generating preimage from block %d to %d\n", startBlock, endBlock)
if number := currentBlockNum; number > endBlock {
fmt.Printf(
"Cropping generate endpoint from %d to %d\n",
endBlock, number,
)
endBlock = number
}
if startBlock >= endBlock {
fmt.Printf(
"Cropping generate startpoint from %d to %d\n",
startBlock, endBlock-earliestBlock,
)
startBlock = endBlock - earliestBlock
}
// these are by default not blocking
return core.GeneratePreimages(s.hmy.BlockChain, startBlock, endBlock)
}

@ -42,7 +42,7 @@ const (
var (
// HTTPModules ..
HTTPModules = []string{"hmy", "hmyv2", "eth", "debug", "trace", netNamespace, netV1Namespace, netV2Namespace, web3Namespace, "explorer"}
HTTPModules = []string{"hmy", "hmyv2", "eth", "debug", "trace", netNamespace, netV1Namespace, netV2Namespace, web3Namespace, "explorer", "preimages"}
// WSModules ..
WSModules = []string{"hmy", "hmyv2", "eth", "debug", "trace", netNamespace, netV1Namespace, netV2Namespace, web3Namespace, "web3"}

Loading…
Cancel
Save