Compare commits

...

25 Commits

Author SHA1 Message Date
static 514d58ebc8 troubleshooting engine_test issues 12 months ago
static 8fc39df5d9 fix config.go conflict 12 months ago
static ad438fd5dc Merge branch 'dev' into feature/clear-stale-staking-data 12 months ago
Diego Nava 5215f6d0d1
lint issues 12 months ago
Diego Nava 652d2b7c9c
lint issues 12 months ago
Diego Nava 09080397b4
merge dev 12 months ago
static e28505f237 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 910a09dbcf Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 5c512f62ed Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 76f0ef6974 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 5a1bc7a226 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 1b3b181b09 fix merge conflict 1 year ago
static 7262b56567 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 3d7a9318d1 update leader rotation configs 1 year ago
static 61cbbdd152 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 912aeb6a50 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 3687575bcd fix merge conflict and syncing with dev 1 year ago
static 801716261d fix merge conflict 1 year ago
static 1954fa8295 fix merge conflicts 1 year ago
static 0a52b14af1 Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static 2ff63764c5 update statedb, reward:AccumulateRewardsAndCountSigs 1 year ago
static 3dce4b7058 update statedb, reward:AccumulateRewardsAndCountSigs 1 year ago
static d9cb44aaa0 add engine_test 1 year ago
Casey Gardiner 90e71609e0
Merge branch 'dev' into feature/clear-stale-staking-data 1 year ago
static fd2890e4d6 Port over old clear stale stake data commits from stale PR (buildable) 1 year ago
  1. 2
      consensus/engine/consensus_engine.go
  2. 6
      core/blockchain.go
  3. 26
      core/blockchain_impl.go
  4. 6
      core/blockchain_stub.go
  5. 13
      core/chain_makers.go
  6. 2
      core/evm.go
  7. 49
      core/evm_test.go
  8. 33
      core/offchain.go
  9. 2
      core/preimages.go
  10. 9
      core/staking_verifier.go
  11. 68
      core/staking_verifier_test.go
  12. 89
      core/state/statedb.go
  13. 56
      core/state_processor.go
  14. 2
      core/tx_pool.go
  15. 2
      core/types.go
  16. 3
      core/vm/interface.go
  17. 2
      go.mod
  18. 4
      go.sum
  19. 4
      hmy/downloader/adapter_test.go
  20. 4
      hmy/tracer.go
  21. 129
      internal/chain/engine.go
  22. 199
      internal/chain/engine_test.go
  23. 72
      internal/chain/reward.go
  24. 38
      internal/params/config.go
  25. 47
      node/worker/worker.go
  26. 23
      staking/types/delegation.go
  27. 20
      staking/types/delegation_test.go
  28. 2
      test/chain/reward/main.go

@ -151,5 +151,5 @@ type Engine interface {
receipts []*types.Receipt, outcxs []*types.CXReceipt, receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks staking.StakingTransactions, incxs []*types.CXReceiptsProof, stks staking.StakingTransactions,
doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64, doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64,
) (*types.Block, reward.Reader, error) ) (*types.Block, map[common.Address][]common.Address, reward.Reader, error)
} }

@ -100,6 +100,10 @@ type BlockChain interface {
// Rollback is designed to remove a chain of links from the database that aren't // Rollback is designed to remove a chain of links from the database that aren't
// certain enough to be valid. // certain enough to be valid.
Rollback(chain []common.Hash) error Rollback(chain []common.Hash) error
// WriteBlockWithoutState writes only the block and its metadata to the database,
// but does not write any state. This is used to construct competing side forks
// up to the point where they exceed the canonical total difficulty.
// GetMaxGarbageCollectedBlockNumber .. // GetMaxGarbageCollectedBlockNumber ..
GetMaxGarbageCollectedBlockNumber() int64 GetMaxGarbageCollectedBlockNumber() int64
// InsertChain attempts to insert the given batch of blocks in to the canonical // InsertChain attempts to insert the given batch of blocks in to the canonical
@ -274,6 +278,7 @@ type BlockChain interface {
UpdateStakingMetaData( UpdateStakingMetaData(
batch rawdb.DatabaseWriter, block *types.Block, batch rawdb.DatabaseWriter, block *types.Block,
stakeMsgs []types2.StakeMsg, stakeMsgs []types2.StakeMsg,
delegationsToRemove map[common.Address][]common.Address,
state *state.DB, epoch, newEpoch *big.Int, state *state.DB, epoch, newEpoch *big.Int,
) (newValidators []common.Address, err error) ) (newValidators []common.Address, err error)
// ReadBlockRewardAccumulator must only be called on beaconchain // ReadBlockRewardAccumulator must only be called on beaconchain
@ -314,6 +319,7 @@ type BlockChain interface {
receipts []*types.Receipt, receipts []*types.Receipt,
cxReceipts []*types.CXReceipt, cxReceipts []*types.CXReceipt,
stakeMsgs []types2.StakeMsg, stakeMsgs []types2.StakeMsg,
delegationsToRemove map[common.Address][]common.Address,
payout reward.Reader, payout reward.Reader,
state *state.DB, state *state.DB,
) (status WriteStatus, err error) ) (status WriteStatus, err error)

@ -534,7 +534,7 @@ func (bc *BlockChainImpl) validateNewBlock(block *types.Block) error {
// NOTE Order of mutating state here matters. // NOTE Order of mutating state here matters.
// Process block using the parent state as reference point. // Process block using the parent state as reference point.
// Do not read cache from processor. // Do not read cache from processor.
receipts, cxReceipts, _, _, usedGas, _, _, err := bc.processor.Process( receipts, cxReceipts, _, _, _, usedGas, _, _, err := bc.processor.Process(
block, state, bc.vmConfig, false, block, state, bc.vmConfig, false,
) )
if err != nil { if err != nil {
@ -1198,6 +1198,7 @@ func (bc *BlockChainImpl) writeBlockWithState(
block *types.Block, receipts []*types.Receipt, block *types.Block, receipts []*types.Receipt,
cxReceipts []*types.CXReceipt, cxReceipts []*types.CXReceipt,
stakeMsgs []staking.StakeMsg, stakeMsgs []staking.StakeMsg,
delegationsToRemove map[common.Address][]common.Address,
paid reward.Reader, paid reward.Reader,
state *state.DB, state *state.DB,
) (status WriteStatus, err error) { ) (status WriteStatus, err error) {
@ -1294,7 +1295,7 @@ func (bc *BlockChainImpl) writeBlockWithState(
// Write offchain data // Write offchain data
if status, err := bc.CommitOffChainData( if status, err := bc.CommitOffChainData(
batch, block, receipts, batch, block, receipts,
cxReceipts, stakeMsgs, cxReceipts, stakeMsgs, delegationsToRemove,
paid, state, paid, state,
); err != nil { ); err != nil {
return status, err return status, err
@ -1547,7 +1548,7 @@ func (bc *BlockChainImpl) insertChain(chain types.Blocks, verifyHeaders bool) (i
} }
// Process block using the parent state as reference point. // Process block using the parent state as reference point.
substart := time.Now() substart := time.Now()
receipts, cxReceipts, stakeMsgs, logs, usedGas, payout, newState, err := bc.processor.Process( receipts, cxReceipts, stakeMsgs, delegationsToRemove, logs, usedGas, payout, newState, err := bc.processor.Process(
block, state, vmConfig, true, block, state, vmConfig, true,
) )
state = newState // update state in case the new state is cached. state = newState // update state in case the new state is cached.
@ -1584,7 +1585,7 @@ func (bc *BlockChainImpl) insertChain(chain types.Blocks, verifyHeaders bool) (i
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
substart = time.Now() substart = time.Now()
status, err := bc.writeBlockWithState( status, err := bc.writeBlockWithState(
block, receipts, cxReceipts, stakeMsgs, payout, state, block, receipts, cxReceipts, stakeMsgs, delegationsToRemove, payout, state,
) )
if err != nil { if err != nil {
return i, events, coalescedLogs, err return i, events, coalescedLogs, err
@ -2687,6 +2688,7 @@ func (bc *BlockChainImpl) writeDelegationsByDelegator(
func (bc *BlockChainImpl) UpdateStakingMetaData( func (bc *BlockChainImpl) UpdateStakingMetaData(
batch rawdb.DatabaseWriter, block *types.Block, batch rawdb.DatabaseWriter, block *types.Block,
stakeMsgs []staking.StakeMsg, stakeMsgs []staking.StakeMsg,
delegationsToRemove map[common.Address][]common.Address,
state *state.DB, epoch, newEpoch *big.Int, state *state.DB, epoch, newEpoch *big.Int,
) (newValidators []common.Address, err error) { ) (newValidators []common.Address, err error) {
newValidators, newDelegations, err := bc.prepareStakingMetaData(block, stakeMsgs, state) newValidators, newDelegations, err := bc.prepareStakingMetaData(block, stakeMsgs, state)
@ -2741,6 +2743,13 @@ func (bc *BlockChainImpl) UpdateStakingMetaData(
if err := bc.writeDelegationsByDelegator(batch, addr, delegations); err != nil { if err := bc.writeDelegationsByDelegator(batch, addr, delegations); err != nil {
return newValidators, err return newValidators, err
} }
for delegatorAddress, validatorAddresses := range delegationsToRemove {
if err := bc.RemoveDelegationsFromDelegator(batch, delegatorAddress, validatorAddresses); err != nil {
return newValidators, err
}
}
} }
return newValidators, nil return newValidators, nil
} }
@ -2771,7 +2780,7 @@ func (bc *BlockChainImpl) prepareStakingMetaData(
return nil, nil, err return nil, nil, err
} }
} else { } else {
panic("Only *staking.Delegate stakeMsgs are supported at the moment") return nil, nil, errors.New("Only *staking.Delegate stakeMsgs are supported at the moment")
} }
} }
for _, txn := range block.StakingTransactions() { for _, txn := range block.StakingTransactions() {
@ -2899,7 +2908,7 @@ func (bc *BlockChainImpl) addDelegationIndex(
} }
} }
// Found the delegation from state and add the delegation index // Find the delegation from state and add the delegation index (the position in validator)
// Note this should read from the state of current block in concern // Note this should read from the state of current block in concern
wrapper, err := state.ValidatorWrapper(validatorAddress, true, false) wrapper, err := state.ValidatorWrapper(validatorAddress, true, false)
if err != nil { if err != nil {
@ -2915,6 +2924,11 @@ func (bc *BlockChainImpl) addDelegationIndex(
Index: uint64(i), Index: uint64(i),
BlockNum: blockNum, BlockNum: blockNum,
}) })
// wrapper.Delegations will not have another delegator
// with the same address, so we are done
break
} }
} }
return delegations, nil return delegations, nil

@ -124,7 +124,7 @@ func (a Stub) WriteBlockWithoutState(block *types.Block, td *big.Int) (err error
return errors.Errorf("method WriteBlockWithoutState not implemented for %s", a.Name) return errors.Errorf("method WriteBlockWithoutState not implemented for %s", a.Name)
} }
func (a Stub) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, cxReceipts []*types.CXReceipt, stakeMsgs []staking.StakeMsg, paid reward.Reader, state *state.DB) (status WriteStatus, err error) { func (a Stub) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, cxReceipts []*types.CXReceipt, stakeMsgs []staking.StakeMsg, delegationsToRemove map[common.Address][]common.Address, paid reward.Reader, state *state.DB) (status WriteStatus, err error) {
return 0, errors.Errorf("method WriteBlockWithState not implemented for %s", a.Name) return 0, errors.Errorf("method WriteBlockWithState not implemented for %s", a.Name)
} }
@ -384,7 +384,7 @@ func (a Stub) ReadDelegationsByDelegatorAt(delegator common.Address, blockNum *b
return nil, errors.Errorf("method ReadDelegationsByDelegatorAt not implemented for %s", a.Name) return nil, errors.Errorf("method ReadDelegationsByDelegatorAt not implemented for %s", a.Name)
} }
func (a Stub) UpdateStakingMetaData(batch rawdb.DatabaseWriter, block *types.Block, stakeMsgs []staking.StakeMsg, state *state.DB, epoch, newEpoch *big.Int) (newValidators []common.Address, err error) { func (a Stub) UpdateStakingMetaData(batch rawdb.DatabaseWriter, block *types.Block, stakeMsgs []staking.StakeMsg, delegationsToRemove map[common.Address][]common.Address, state *state.DB, epoch, newEpoch *big.Int) (newValidators []common.Address, err error) {
return nil, errors.Errorf("method UpdateStakingMetaData not implemented for %s", a.Name) return nil, errors.Errorf("method UpdateStakingMetaData not implemented for %s", a.Name)
} }
@ -423,7 +423,7 @@ func (a Stub) IsEnablePruneBeaconChainFeature() bool {
return false return false
} }
func (a Stub) CommitOffChainData(batch rawdb.DatabaseWriter, block *types.Block, receipts []*types.Receipt, cxReceipts []*types.CXReceipt, stakeMsgs []staking.StakeMsg, payout reward.Reader, state *state.DB) (status WriteStatus, err error) { func (a Stub) CommitOffChainData(batch rawdb.DatabaseWriter, block *types.Block, receipts []*types.Receipt, cxReceipts []*types.CXReceipt, stakeMsgs []staking.StakeMsg, delegationsToRemove map[common.Address][]common.Address, payout reward.Reader, state *state.DB) (status WriteStatus, err error) {
return 0, errors.Errorf("method CommitOffChainData not implemented for %s", a.Name) return 0, errors.Errorf("method CommitOffChainData not implemented for %s", a.Name)
} }

@ -14,16 +14,17 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package chain package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
blockfactory "github.com/harmony-one/harmony/block/factory" blockfactory "github.com/harmony-one/harmony/block/factory"
@ -32,7 +33,7 @@ import (
"github.com/harmony-one/harmony/core/state/snapshot" "github.com/harmony-one/harmony/core/state/snapshot"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
) )
@ -104,7 +105,7 @@ func (b *BlockGen) AddTxWithChain(bc core.BlockChain, tx *types.Transaction) {
b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
coinbase := b.header.Coinbase() coinbase := b.header.Coinbase()
gasUsed := b.header.GasUsed() gasUsed := b.header.GasUsed()
receipt, _, _, _, err := core.ApplyTransaction(bc, &coinbase, b.gasPool, b.statedb, b.header, tx, &gasUsed, vm.Config{}) receipt, _, _, _, err := ApplyTransaction(bc, &coinbase, b.gasPool, b.statedb, b.header, tx, &gasUsed, vm.Config{})
b.header.SetGasUsed(gasUsed) b.header.SetGasUsed(gasUsed)
b.header.SetCoinbase(coinbase) b.header.SetCoinbase(coinbase)
if err != nil { if err != nil {
@ -185,7 +186,7 @@ func GenerateChain(
if b.engine != nil { if b.engine != nil {
// Finalize and seal the block // Finalize and seal the block
block, _, err := b.engine.Finalize( block, _, _, err := b.engine.Finalize(
chainreader, nil, b.header, statedb, b.txs, b.receipts, nil, nil, nil, nil, nil, func() uint64 { return 0 }, chainreader, nil, b.header, statedb, b.txs, b.receipts, nil, nil, nil, nil, nil, func() uint64 { return 0 },
) )
if err != nil { if err != nil {
@ -229,7 +230,7 @@ func makeHeader(chain consensus_engine.ChainReader, parent *block.Header, state
Root(state.IntermediateRoot(chain.Config().IsS3(parent.Epoch()))). Root(state.IntermediateRoot(chain.Config().IsS3(parent.Epoch()))).
ParentHash(parent.Hash()). ParentHash(parent.Hash()).
Coinbase(parent.Coinbase()). Coinbase(parent.Coinbase()).
GasLimit(core.CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit())). GasLimit(core.CalcGasLimit(parent.GasLimit(), parent.GasLimit())).
Number(new(big.Int).Add(parent.Number(), common.Big1)). Number(new(big.Int).Add(parent.Number(), common.Big1)).
Time(time). Time(time).
Header() Header()

@ -243,7 +243,7 @@ func DelegateFn(ref *block.Header, chain ChainContext) vm.DelegateFunc {
func UndelegateFn(ref *block.Header, chain ChainContext) vm.UndelegateFunc { func UndelegateFn(ref *block.Header, chain ChainContext) vm.UndelegateFunc {
// moved from state_transition.go to here, with some modifications // moved from state_transition.go to here, with some modifications
return func(db vm.StateDB, rosettaTracer vm.RosettaTracer, undelegate *stakingTypes.Undelegate) error { return func(db vm.StateDB, rosettaTracer vm.RosettaTracer, undelegate *stakingTypes.Undelegate) error {
wrapper, err := VerifyAndUndelegateFromMsg(db, ref.Epoch(), undelegate) wrapper, err := VerifyAndUndelegateFromMsg(db, ref.Epoch(), chain.Config(), undelegate)
if err != nil { if err != nil {
return err return err
} }

@ -32,7 +32,7 @@ func getTestEnvironment(testBankKey ecdsa.PrivateKey) (*BlockChainImpl, *state.D
var ( var (
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(40000)) testBankFunds = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(40000))
chainConfig = params.TestChainConfig chainConfig = params.LocalnetChainConfig
blockFactory = blockfactory.ForTest blockFactory = blockfactory.ForTest
database = rawdb.NewMemoryDatabase() database = rawdb.NewMemoryDatabase()
gspec = Genesis{ gspec = Genesis{
@ -50,7 +50,7 @@ func getTestEnvironment(testBankKey ecdsa.PrivateKey) (*BlockChainImpl, *state.D
chain, _ := NewBlockChain(database, nil, nil, cacheConfig, gspec.Config, engine, vm.Config{}) chain, _ := NewBlockChain(database, nil, nil, cacheConfig, gspec.Config, engine, vm.Config{})
db, _ := chain.StateAt(genesis.Root()) db, _ := chain.StateAt(genesis.Root())
// make a fake block header (use epoch 1 so that locked tokens can be tested) // make a fake block header
header := blockFactory.NewHeader(common.Big0) header := blockFactory.NewHeader(common.Big0)
return chain, db, header, database return chain, db, header, database
@ -119,6 +119,51 @@ func TestEVMStaking(t *testing.T) {
t.Errorf("Got error %v in Undelegate", err) t.Errorf("Got error %v in Undelegate", err)
} }
// undelegate test - epoch 3 to test NoNilDelegationsEpoch case
delegatorKey, _ := crypto.GenerateKey()
ctx3 := NewEVMContext(msg, blockfactory.ForTest.NewHeader(common.Big3), chain, nil)
delegate = sampleDelegate(*delegatorKey) // 1000 ONE
db.AddBalance(delegate.DelegatorAddress, delegate.Amount)
delegate.ValidatorAddress = wrapper.Address
err = ctx.Delegate(db, nil, &delegate)
if err != nil {
t.Errorf("Got error %v in Delegate for new delegator", err)
}
undelegate = sampleUndelegate(*delegatorKey)
// try undelegating such that remaining < minimum (100 ONE)
undelegate.ValidatorAddress = wrapper.Address
undelegate.Amount = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(901))
err = ctx3.Undelegate(db, nil, &undelegate)
if err == nil {
t.Errorf("Got no error in Undelegate for new delegator")
} else {
if err.Error() != "Minimum: 100000000000000000000, Remaining: 99000000000000000000: remaining delegation must be 0 or >= 100 ONE" {
t.Errorf("Got error %v but expected %v", err, staking.ErrUndelegationRemaining)
}
}
// now undelegate such that remaining == minimum (100 ONE)
undelegate.Amount = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(900))
err = ctx3.Undelegate(db, nil, &undelegate)
if err != nil {
t.Errorf("Got error %v in Undelegate for new delegator", err)
}
// remaining < 100 ONE after remaining = minimum
undelegate.Amount = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(1))
err = ctx3.Undelegate(db, nil, &undelegate)
if err == nil {
t.Errorf("Got no error in Undelegate for new delegator")
} else {
if err.Error() != "Minimum: 100000000000000000000, Remaining: 99000000000000000000: remaining delegation must be 0 or >= 100 ONE" {
t.Errorf("Got error %v but expected %v", err, staking.ErrUndelegationRemaining)
}
}
// remaining == 0
undelegate.Amount = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100))
err = ctx3.Undelegate(db, nil, &undelegate)
if err != nil {
t.Errorf("Got error %v in Undelegate for new delegator", err)
}
// collectRewards test // collectRewards test
collectRewards := sampleCollectRewards(*key) collectRewards := sampleCollectRewards(*key)
// add block rewards to make sure there are some to collect // add block rewards to make sure there are some to collect

@ -28,6 +28,7 @@ func (bc *BlockChainImpl) CommitOffChainData(
receipts []*types.Receipt, receipts []*types.Receipt,
cxReceipts []*types.CXReceipt, cxReceipts []*types.CXReceipt,
stakeMsgs []staking.StakeMsg, stakeMsgs []staking.StakeMsg,
delegationsToRemove map[common.Address][]common.Address,
payout reward.Reader, payout reward.Reader,
state *state.DB, state *state.DB,
) (status WriteStatus, err error) { ) (status WriteStatus, err error) {
@ -118,7 +119,7 @@ func (bc *BlockChainImpl) CommitOffChainData(
// Do bookkeeping for new staking txns // Do bookkeeping for new staking txns
newVals, err := bc.UpdateStakingMetaData( newVals, err := bc.UpdateStakingMetaData(
batch, block, stakeMsgs, state, epoch, nextBlockEpoch, batch, block, stakeMsgs, delegationsToRemove, state, epoch, nextBlockEpoch,
) )
if err != nil { if err != nil {
utils.Logger().Err(err).Msg("UpdateStakingMetaData failed") utils.Logger().Err(err).Msg("UpdateStakingMetaData failed")
@ -327,3 +328,33 @@ func (bc *BlockChainImpl) getNextBlockEpoch(header *block.Header) (*big.Int, err
} }
return nextBlockEpoch, nil return nextBlockEpoch, nil
} }
func (bc *BlockChainImpl) RemoveDelegationsFromDelegator(
batch rawdb.DatabaseWriter,
delegatorAddress common.Address,
validatorAddresses []common.Address,
) error {
delegationIndexes, err := bc.ReadDelegationsByDelegator(delegatorAddress)
if err != nil {
return err
}
finalDelegationIndexes := delegationIndexes[:0]
for _, validatorAddress := range validatorAddresses {
// TODO: can this be sped up from O(vd) to something shorter?
for _, delegationIndex := range delegationIndexes {
if bytes.Equal(
validatorAddress.Bytes(),
delegationIndex.ValidatorAddress.Bytes(),
) {
// do nothing
break
}
finalDelegationIndexes = append(
finalDelegationIndexes,
delegationIndex,
)
}
}
bc.writeDelegationsByDelegator(batch, delegatorAddress, finalDelegationIndexes)
return nil
}

@ -226,7 +226,7 @@ func GeneratePreimages(chain BlockChain, start, end uint64) error {
return fmt.Errorf("block %d not found", i) return fmt.Errorf("block %d not found", i)
} }
stateAt, _ := chain.StateAt(block.Root()) stateAt, _ := chain.StateAt(block.Root())
_, _, _, _, _, _, endingState, errProcess = chain.Processor().Process(block, startingState, *chain.GetVMConfig(), false) _, _, _, _, _, _, _, endingState, errProcess = chain.Processor().Process(block, startingState, *chain.GetVMConfig(), false)
if errProcess != nil { if errProcess != nil {
return fmt.Errorf("error executing block #%d: %s", i, errProcess) return fmt.Errorf("error executing block #%d: %s", i, errProcess)
} }

@ -367,7 +367,7 @@ func VerifyAndDelegateFromMsg(
// //
// Note that this function never updates the stateDB, it only reads from stateDB. // Note that this function never updates the stateDB, it only reads from stateDB.
func VerifyAndUndelegateFromMsg( func VerifyAndUndelegateFromMsg(
stateDB vm.StateDB, epoch *big.Int, msg *staking.Undelegate, stateDB vm.StateDB, epoch *big.Int, chainConfig *params.ChainConfig, msg *staking.Undelegate,
) (*staking.ValidatorWrapper, error) { ) (*staking.ValidatorWrapper, error) {
if stateDB == nil { if stateDB == nil {
return nil, errStateDBIsMissing return nil, errStateDBIsMissing
@ -389,10 +389,15 @@ func VerifyAndUndelegateFromMsg(
return nil, err return nil, err
} }
var minimumRemainingDelegation *big.Int
if chainConfig.IsNoNilDelegations(epoch) {
minimumRemainingDelegation = minimumDelegationV2 // 100 ONE
}
for i := range wrapper.Delegations { for i := range wrapper.Delegations {
delegation := &wrapper.Delegations[i] delegation := &wrapper.Delegations[i]
if bytes.Equal(delegation.DelegatorAddress.Bytes(), msg.DelegatorAddress.Bytes()) { if bytes.Equal(delegation.DelegatorAddress.Bytes(), msg.DelegatorAddress.Bytes()) {
if err := delegation.Undelegate(epoch, msg.Amount); err != nil { if err := delegation.Undelegate(epoch, msg.Amount, minimumRemainingDelegation); err != nil {
return nil, err return nil, err
} }
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(); err != nil {

@ -1194,8 +1194,9 @@ func TestVerifyAndUndelegateFromMsg(t *testing.T) {
epoch *big.Int epoch *big.Int
msg staking.Undelegate msg staking.Undelegate
expVWrapper staking.ValidatorWrapper expVWrapper staking.ValidatorWrapper
expErr error expErr error
noNilDelegationsEpoch *big.Int
}{ }{
{ {
// 0: Undelegate at delegation with an entry already exist at the same epoch. // 0: Undelegate at delegation with an entry already exist at the same epoch.
@ -1362,9 +1363,68 @@ func TestVerifyAndUndelegateFromMsg(t *testing.T) {
expErr: errNoDelegationToUndelegate, expErr: errNoDelegationToUndelegate,
}, },
{
// 12: Undelegate with NoNilDelegationsEpoch set
// such that remaining < minimum
sdb: makeDefaultStateForUndelegate(t), // delegatorAddr has 15k ones delegated
epoch: big.NewInt(defaultEpoch),
msg: func() staking.Undelegate {
msg := defaultMsgUndelegate()
msg.Amount = new(big.Int).Mul(oneBig, big.NewInt(14901))
return msg
}(),
expVWrapper: makeDefaultSnapVWrapperForUndelegate(t),
expErr: staking.ErrUndelegationRemaining,
noNilDelegationsEpoch: big.NewInt(defaultEpoch),
},
{
// 13: Undelegate with NoNilDelegationsEpoch set
// such that remaining == minimum
// delegatorAddr has 15k ones delegated and 5k in Undelegations at defaultEpoch
sdb: makeDefaultStateForUndelegate(t),
epoch: big.NewInt(defaultEpoch),
msg: func() staking.Undelegate {
msg := defaultMsgUndelegate()
msg.Amount = new(big.Int).Mul(oneBig, big.NewInt(14900))
return msg
}(),
expVWrapper: func(t *testing.T) staking.ValidatorWrapper {
w := makeDefaultSnapVWrapperForUndelegate(t)
w.Delegations[1].Amount = new(big.Int).Mul(oneBig, big.NewInt(100))
w.Delegations[1].Undelegations[0].Amount = new(big.Int).Mul(oneBig, big.NewInt(19900))
return w
}(t),
noNilDelegationsEpoch: big.NewInt(defaultEpoch),
},
{
// 14: Undelegate with NoNilDelegationsEpoch set
// such that remaining == 0
// delegatorAddr has 15k ones delegated and 5k in Undelegations at defaultEpoch
sdb: makeDefaultStateForUndelegate(t),
epoch: big.NewInt(defaultEpoch),
msg: func() staking.Undelegate {
msg := defaultMsgUndelegate()
msg.Amount = fifteenKOnes
return msg
}(),
expVWrapper: func(t *testing.T) staking.ValidatorWrapper {
w := makeDefaultSnapVWrapperForUndelegate(t)
w.Delegations[1].Amount = common.Big0
w.Delegations[1].Undelegations[0].Amount = twentyKOnes
return w
}(t),
noNilDelegationsEpoch: big.NewInt(defaultEpoch),
},
} }
for i, test := range tests { for i, test := range tests {
w, err := VerifyAndUndelegateFromMsg(test.sdb, test.epoch, &test.msg) config := &params.ChainConfig{}
if test.noNilDelegationsEpoch != nil {
config.NoNilDelegationsEpoch = test.noNilDelegationsEpoch
} else {
config.NoNilDelegationsEpoch = big.NewInt(10000000) // EpochTBD
}
w, err := VerifyAndUndelegateFromMsg(test.sdb, test.epoch, config, &test.msg)
if assErr := assertError(err, test.expErr); assErr != nil { if assErr := assertError(err, test.expErr); assErr != nil {
t.Errorf("Test %v: %v", i, assErr) t.Errorf("Test %v: %v", i, assErr)
@ -1383,7 +1443,7 @@ func makeDefaultSnapVWrapperForUndelegate(t *testing.T) staking.ValidatorWrapper
w := makeVWrapperByIndex(validatorIndex) w := makeVWrapperByIndex(validatorIndex)
newDelegation := staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)) newDelegation := staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes))
if err := newDelegation.Undelegate(big.NewInt(defaultEpoch), fiveKOnes); err != nil { if err := newDelegation.Undelegate(big.NewInt(defaultEpoch), fiveKOnes, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
w.Delegations = append(w.Delegations, newDelegation) w.Delegations = append(w.Delegations, newDelegation)

@ -18,6 +18,7 @@
package state package state
import ( import (
"bytes"
"fmt" "fmt"
"math/big" "math/big"
"sort" "sort"
@ -1234,7 +1235,7 @@ func (db *DB) ValidatorWrapper(
) (*stk.ValidatorWrapper, error) { ) (*stk.ValidatorWrapper, error) {
// if cannot revert and ask for a copy // if cannot revert and ask for a copy
if sendOriginal && copyDelegations { if sendOriginal && copyDelegations {
panic("'Cannot revert' must not expect copy of delegations") return nil, errors.New("'sendOriginal' must not expect copy of delegations")
} }
// Read cache first // Read cache first
@ -1368,6 +1369,7 @@ func (db *DB) AddReward(
snapshot *stk.ValidatorWrapper, snapshot *stk.ValidatorWrapper,
reward *big.Int, reward *big.Int,
shareLookup map[common.Address]numeric.Dec, shareLookup map[common.Address]numeric.Dec,
nilDelegationsRemoved bool,
) error { ) error {
if reward.Cmp(common.Big0) == 0 { if reward.Cmp(common.Big0) == 0 {
utils.Logger().Info().RawJSON("validator", []byte(snapshot.String())). utils.Logger().Info().RawJSON("validator", []byte(snapshot.String())).
@ -1375,6 +1377,10 @@ func (db *DB) AddReward(
return nil return nil
} }
if len(snapshot.Delegations) != len(shareLookup) {
return errors.New("[AddReward] Snapshot and shareLookup mismatch")
}
curValidator, err := db.ValidatorWrapper(snapshot.Address, true, false) curValidator, err := db.ValidatorWrapper(snapshot.Address, true, false)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to distribute rewards: validator does not exist") return errors.Wrapf(err, "failed to distribute rewards: validator does not exist")
@ -1399,24 +1405,85 @@ func (db *DB) AddReward(
rewardPool.Sub(rewardPool, commissionInt) rewardPool.Sub(rewardPool, commissionInt)
} }
// no sanity check for length of delegations between curValidator and snapshot
// if a delegation happens, len(curValidator) > len(snapshot)
// if it doesn't happen, curValidator == snapshot
// if there are only removals, curValidator < snapshot
// Payout each delegator's reward pro-rata // Payout each delegator's reward pro-rata
totalRewardForDelegators := big.NewInt(0).Set(rewardPool) totalRewardForDelegators := big.NewInt(0).Set(rewardPool)
for i := range snapshot.Delegations { if !nilDelegationsRemoved {
delegation := snapshot.Delegations[i] for i := range snapshot.Delegations {
percentage, ok := shareLookup[delegation.DelegatorAddress] delegation := snapshot.Delegations[i]
percentage, ok := shareLookup[delegation.DelegatorAddress]
if !ok { if !ok {
return errors.Wrapf(err, "missing delegation shares for reward distribution") return errors.Wrapf(err, "missing delegation shares for reward distribution")
}
rewardInt := percentage.MulInt(totalRewardForDelegators).RoundInt()
curDelegation := curValidator.Delegations[i]
// should we check that curDelegation.DelegatorAddress == delegation.DelegatorAddress ?
// wasn't there originally so I leave it for now
curDelegation.Reward.Add(curDelegation.Reward, rewardInt)
rewardPool.Sub(rewardPool, rewardInt)
} }
} else {
// iterate simply over snapshot delegations
// those added later to curValidator are new delegations which do not receive rewards immediately
// those removed from curValidator are stale delegations which do not receive rewards anyway
offset := 0
for i := 0; i < len(snapshot.Delegations); i++ {
delegationFromSnapshot := snapshot.Delegations[i]
percentage, ok := shareLookup[delegationFromSnapshot.DelegatorAddress]
if !ok {
return errors.Wrapf(err, "missing delegation shares for reward distribution")
}
if percentage.IsZero() { // stale delegation
rewardInt := percentage.MulInt(totalRewardForDelegators).RoundInt() offset++
curDelegation := curValidator.Delegations[i] continue
curDelegation.Reward.Add(curDelegation.Reward, rewardInt) }
rewardPool.Sub(rewardPool, rewardInt) // try to find in wrapper
// this is O(N) even though order is not guaranteed
// for example, snapshot is A / B / C / D / E where C is a stale delegation
// wrapper then becomes A / B / D / E
// if C then delegates to this validator, the wrapper becomes A / B / D / E / C
// (1) snapshot = A / B / C / D / E and wrapper = A / B / D / E / C (remove and re-add)
// for i in [0, 1] j = i - 0 (offset) works well
// for i = 2, offset becomes 1
// for i in [3, 4] use j = i - 1 (offset)
// other cases are
// (1) snapshot = A / B / C / D and wrapper = A / C / D (just remove B)
// (2) snapshot = A / B / C / D and wrapper = A / C / D / E (remove B and add E)
// (3) snapshot = A / B / C / D and wrapper = A / B / C / D / E (just add E)
// (4) snapshot and wrapper equal (no effort needed)
// even if a stale delegation is removed from the end and re-added this works
// (5) snapshot = A / B / C / D / E and wrapper = A / B / D / E / C / F (remove and re-add + add)
found := false
for j := i - offset; j < len(curValidator.Delegations) && !found; j++ {
delegationFromWrapper := curValidator.Delegations[j]
if bytes.Equal(
delegationFromWrapper.DelegatorAddress.Bytes(),
delegationFromSnapshot.DelegatorAddress.Bytes(),
) {
found = true
rewardInt := percentage.MulInt(totalRewardForDelegators).RoundInt()
delegationFromWrapper.Reward.Add(delegationFromWrapper.Reward, rewardInt)
rewardPool.Sub(rewardPool, rewardInt)
}
}
// delegation in snapshot with non zero reward but not in wrapper
if !found {
return errors.New("Non-zero reward found in snapshot but delegation missing in wrapper")
}
}
} }
// The last remaining bit belongs to the validator (remember the validator's self delegation is // The last remaining bit belongs to the validator (remember the validator's self delegation is
// always at index 0) // always at index 0). We do not allow validator deletions (yet?) so a validator's
// self stake is never deleted even if otherwise stale
if rewardPool.Cmp(common.Big0) > 0 { if rewardPool.Cmp(common.Big0) > 0 {
curValidator.Delegations[0].Reward.Add(curValidator.Delegations[0].Reward, rewardPool) curValidator.Delegations[0].Reward.Add(curValidator.Delegations[0].Reward, rewardPool)
} }

@ -64,13 +64,14 @@ type StateProcessor struct {
// this structure is cached, and each individual element is returned // this structure is cached, and each individual element is returned
type ProcessorResult struct { type ProcessorResult struct {
Receipts types.Receipts Receipts types.Receipts
CxReceipts types.CXReceipts CxReceipts types.CXReceipts
StakeMsgs []staking.StakeMsg StakeMsgs []staking.StakeMsg
Logs []*types.Log Logs []*types.Log
UsedGas uint64 UsedGas uint64
Reward reward.Reader Reward reward.Reader
State *state.DB State *state.DB
DelegationsToRemove map[common.Address][]common.Address
} }
// NewStateProcessor initialises a new StateProcessor. // NewStateProcessor initialises a new StateProcessor.
@ -104,6 +105,7 @@ func (p *StateProcessor) Process(
block *types.Block, statedb *state.DB, cfg vm.Config, readCache bool, block *types.Block, statedb *state.DB, cfg vm.Config, readCache bool,
) ( ) (
types.Receipts, types.CXReceipts, []staking.StakeMsg, types.Receipts, types.CXReceipts, []staking.StakeMsg,
map[common.Address][]common.Address,
[]*types.Log, UsedGas, reward.Reader, *state.DB, error, []*types.Log, UsedGas, reward.Reader, *state.DB, error,
) { ) {
cacheKey := block.Hash() cacheKey := block.Hash()
@ -113,7 +115,7 @@ func (p *StateProcessor) Process(
// Only the successful results are cached in case for retry. // Only the successful results are cached in case for retry.
result := cached.(*ProcessorResult) result := cached.(*ProcessorResult)
utils.Logger().Info().Str("block num", block.Number().String()).Msg("result cache hit.") utils.Logger().Info().Str("block num", block.Number().String()).Msg("result cache hit.")
return result.Receipts, result.CxReceipts, result.StakeMsgs, result.Logs, result.UsedGas, result.Reward, result.State, nil return result.Receipts, result.CxReceipts, result.StakeMsgs, result.DelegationsToRemove, result.Logs, result.UsedGas, result.Reward, result.State, nil
} }
} }
@ -130,7 +132,7 @@ func (p *StateProcessor) Process(
beneficiary, err := p.bc.GetECDSAFromCoinbase(header) beneficiary, err := p.bc.GetECDSAFromCoinbase(header)
if err != nil { if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err return nil, nil, nil, nil, nil, 0, nil, statedb, err
} }
processTxsAndStxs := true processTxsAndStxs := true
@ -141,7 +143,7 @@ func (p *StateProcessor) Process(
processTxsAndStxs = false processTxsAndStxs = false
} }
if !errors.Is(err, ErrNoMigrationRequired) && !errors.Is(err, ErrNoMigrationPossible) { if !errors.Is(err, ErrNoMigrationRequired) && !errors.Is(err, ErrNoMigrationPossible) {
return nil, nil, nil, nil, 0, nil, statedb, err return nil, nil, nil, nil, nil, 0, nil, statedb, err
} }
} else { } else {
if cxReceipt != nil { if cxReceipt != nil {
@ -160,7 +162,7 @@ func (p *StateProcessor) Process(
p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
) )
if err != nil { if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err return nil, nil, nil, nil, nil, 0, nil, statedb, err
} }
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
if cxReceipt != nil { if cxReceipt != nil {
@ -183,7 +185,7 @@ func (p *StateProcessor) Process(
p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg, p.bc, &beneficiary, gp, statedb, header, tx, usedGas, cfg,
) )
if err != nil { if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err return nil, nil, nil, nil, nil, 0, nil, statedb, err
} }
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
@ -196,7 +198,7 @@ func (p *StateProcessor) Process(
if err := ApplyIncomingReceipt( if err := ApplyIncomingReceipt(
p.bc.Config(), statedb, header, cx, p.bc.Config(), statedb, header, cx,
); err != nil { ); err != nil {
return nil, nil, return nil, nil, nil,
nil, nil, 0, nil, statedb, errors.New("[Process] Cannot apply incoming receipts") nil, nil, 0, nil, statedb, errors.New("[Process] Cannot apply incoming receipts")
} }
} }
@ -204,14 +206,13 @@ func (p *StateProcessor) Process(
slashes := slash.Records{} slashes := slash.Records{}
if s := header.Slashes(); len(s) > 0 { if s := header.Slashes(); len(s) > 0 {
if err := rlp.DecodeBytes(s, &slashes); err != nil { if err := rlp.DecodeBytes(s, &slashes); err != nil {
return nil, nil, nil, nil, 0, nil, statedb, errors.New( return nil, nil, nil, nil, nil, 0, nil, statedb, errors.Wrap(err,
"[Process] Cannot finalize block", "[Process] Cannot finalize block")
)
} }
} }
if err := MayShardReduction(p.bc, statedb, header); err != nil { if err := MayShardReduction(p.bc, statedb, header); err != nil {
return nil, nil, nil, nil, 0, nil, statedb, err return nil, nil, nil, nil, nil, 0, nil, statedb, err
} }
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@ -220,27 +221,28 @@ func (p *StateProcessor) Process(
// Block processing don't need to block on reward computation as in block proposal // Block processing don't need to block on reward computation as in block proposal
sigsReady <- true sigsReady <- true
}() }()
_, payout, err := p.bc.Engine().Finalize( _, delegationsToRemove, payout, err := p.bc.Engine().Finalize(
p.bc, p.bc,
p.beacon, p.beacon,
header, statedb, block.Transactions(), header, statedb, block.Transactions(),
receipts, outcxs, incxs, block.StakingTransactions(), slashes, sigsReady, func() uint64 { return header.ViewID().Uint64() }, receipts, outcxs, incxs, block.StakingTransactions(), slashes, sigsReady, func() uint64 { return header.ViewID().Uint64() },
) )
if err != nil { if err != nil {
return nil, nil, nil, nil, 0, nil, statedb, errors.WithMessage(err, "[Process] Cannot finalize block") return nil, nil, nil, nil, nil, 0, nil, statedb, errors.WithMessage(err, "[Process] Cannot finalize block")
} }
result := &ProcessorResult{ result := &ProcessorResult{
Receipts: receipts, Receipts: receipts,
CxReceipts: outcxs, CxReceipts: outcxs,
StakeMsgs: blockStakeMsgs, StakeMsgs: blockStakeMsgs,
Logs: allLogs, Logs: allLogs,
UsedGas: *usedGas, UsedGas: *usedGas,
Reward: payout, Reward: payout,
State: statedb, State: statedb,
DelegationsToRemove: delegationsToRemove,
} }
p.resultCache.Add(cacheKey, result) p.resultCache.Add(cacheKey, result)
return receipts, outcxs, blockStakeMsgs, allLogs, *usedGas, payout, statedb, nil return receipts, outcxs, blockStakeMsgs, delegationsToRemove, allLogs, *usedGas, payout, statedb, nil
} }
// CacheProcessorResult caches the process result on the cache key. // CacheProcessorResult caches the process result on the cache key.

@ -933,7 +933,7 @@ func (pool *TxPool) validateStakingTx(tx *staking.StakingTransaction) error {
return errors.WithMessagef(ErrInvalidSender, "staking transaction sender is %s", b32) return errors.WithMessagef(ErrInvalidSender, "staking transaction sender is %s", b32)
} }
_, err = VerifyAndUndelegateFromMsg(pool.currentState, pool.pendingEpoch(), stkMsg) _, err = VerifyAndUndelegateFromMsg(pool.currentState, pool.pendingEpoch(), pool.chainconfig, stkMsg)
return err return err
case staking.DirectiveCollectRewards: case staking.DirectiveCollectRewards:
msg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards) msg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards)

@ -17,6 +17,7 @@
package core package core
import ( import (
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/consensus/reward" "github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
@ -55,6 +56,7 @@ type Validator interface {
type Processor interface { type Processor interface {
Process(block *types.Block, statedb *state.DB, cfg vm.Config, readCache bool) ( Process(block *types.Block, statedb *state.DB, cfg vm.Config, readCache bool) (
types.Receipts, types.CXReceipts, []stakingTypes.StakeMsg, types.Receipts, types.CXReceipts, []stakingTypes.StakeMsg,
map[common.Address][]common.Address,
[]*types.Log, uint64, reward.Reader, *state.DB, error, []*types.Log, uint64, reward.Reader, *state.DB, error,
) )
CacheProcessorResult(cacheKey interface{}, result *ProcessorResult) CacheProcessorResult(cacheKey interface{}, result *ProcessorResult)

@ -49,8 +49,7 @@ type StateDB interface {
UnsetValidatorFlag(common.Address) UnsetValidatorFlag(common.Address)
IsValidator(common.Address) bool IsValidator(common.Address) bool
GetValidatorFirstElectionEpoch(addr common.Address) *big.Int GetValidatorFirstElectionEpoch(addr common.Address) *big.Int
AddReward(*staking.ValidatorWrapper, *big.Int, map[common.Address]numeric.Dec) error AddReward(*staking.ValidatorWrapper, *big.Int, map[common.Address]numeric.Dec, bool) error
AddRefund(uint64) AddRefund(uint64)
SubRefund(uint64) SubRefund(uint64)
GetRefund() uint64 GetRefund() uint64

@ -68,7 +68,6 @@ require (
require ( require (
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/grafana/pyroscope-go v1.0.4
github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/bloomfilter/v2 v2.0.3
github.com/ledgerwatch/erigon-lib v0.0.0-20230607152933-42c9c28cac68 github.com/ledgerwatch/erigon-lib v0.0.0-20230607152933-42c9c28cac68
github.com/ledgerwatch/log/v3 v3.8.0 github.com/ledgerwatch/log/v3 v3.8.0
@ -148,7 +147,6 @@ require (
github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 // indirect github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect

@ -632,10 +632,6 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0=
github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY=
github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk=
github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=

@ -160,8 +160,8 @@ func (e *dummyEngine) Finalize(
receipts []*types.Receipt, outcxs []*types.CXReceipt, receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks staking.StakingTransactions, incxs []*types.CXReceiptsProof, stks staking.StakingTransactions,
doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64, doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64,
) (*types.Block, reward.Reader, error) { ) (*types.Block, map[common.Address][]common.Address, reward.Reader, error) {
return nil, nil, nil return nil, nil, nil, nil
} }
type testInsertHelper struct { type testInsertHelper struct {

@ -281,7 +281,7 @@ func (hmy *Harmony) TraceChain(ctx context.Context, start, end *types.Block, con
traced += uint64(len(txs)) traced += uint64(len(txs))
} }
// Generate the next state snapshot fast without tracing // Generate the next state snapshot fast without tracing
_, _, _, _, _, _, _, err := hmy.BlockChain.Processor().Process(block, statedb, vm.Config{}, false) _, _, _, _, _, _, _, _, err := hmy.BlockChain.Processor().Process(block, statedb, vm.Config{}, false)
if err != nil { if err != nil {
failed = err failed = err
break break
@ -676,7 +676,7 @@ func (hmy *Harmony) ComputeStateDB(block *types.Block, reexec uint64) (*state.DB
if block = hmy.BlockChain.GetBlockByNumber(block.NumberU64() + 1); block == nil { if block = hmy.BlockChain.GetBlockByNumber(block.NumberU64() + 1); block == nil {
return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1)
} }
_, _, _, _, _, _, _, err := hmy.BlockChain.Processor().Process(block, statedb, vm.Config{}, false) _, _, _, _, _, _, _, _, err := hmy.BlockChain.Processor().Process(block, statedb, vm.Config{}, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
} }

@ -269,7 +269,7 @@ func (e *engineImpl) Finalize(
receipts []*types.Receipt, outcxs []*types.CXReceipt, receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks staking.StakingTransactions, incxs []*types.CXReceiptsProof, stks staking.StakingTransactions,
doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64, doubleSigners slash.Records, sigsReady chan bool, viewID func() uint64,
) (*types.Block, reward.Reader, error) { ) (*types.Block, map[common.Address][]common.Address, reward.Reader, error) {
isBeaconChain := header.ShardID() == shard.BeaconChainShardID isBeaconChain := header.ShardID() == shard.BeaconChainShardID
inStakingEra := chain.Config().IsStaking(header.Epoch()) inStakingEra := chain.Config().IsStaking(header.Epoch())
@ -279,22 +279,22 @@ func (e *engineImpl) Finalize(
if IsCommitteeSelectionBlock(chain, header) { if IsCommitteeSelectionBlock(chain, header) {
startTime := time.Now() startTime := time.Now()
if err := payoutUndelegations(chain, header, state); err != nil { if err := payoutUndelegations(chain, header, state); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("PayoutUndelegations") utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("PayoutUndelegations")
// Needs to be after payoutUndelegations because payoutUndelegations // Needs to be after payoutUndelegations because payoutUndelegations
// depends on the old LastEpochInCommittee // depends on the old LastEpochInCommittee
startTime = time.Now() startTime = time.Now()
if err := setElectionEpochAndMinFee(chain, header, state, chain.Config()); err != nil { if err := setElectionEpochAndMinFee(chain, header, state, chain.Config()); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("SetElectionEpochAndMinFee") utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("SetElectionEpochAndMinFee")
curShardState, err := chain.ReadShardState(chain.CurrentBlock().Epoch()) curShardState, err := chain.ReadShardState(chain.CurrentBlock().Epoch())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
startTime = time.Now() startTime = time.Now()
// Needs to be before AccumulateRewardsAndCountSigs because // Needs to be before AccumulateRewardsAndCountSigs because
@ -305,7 +305,7 @@ func (e *engineImpl) Finalize(
if err := availability.ComputeAndMutateEPOSStatus( if err := availability.ComputeAndMutateEPOSStatus(
chain, state, addr, chain, state, addr,
); err != nil { ); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("ComputeAndMutateEPOSStatus") utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("ComputeAndMutateEPOSStatus")
@ -317,16 +317,16 @@ func (e *engineImpl) Finalize(
chain, state, header, beacon, sigsReady, chain, state, header, beacon, sigsReady,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// Apply slashes // Apply slashes
if isBeaconChain && inStakingEra && len(doubleSigners) > 0 { if isBeaconChain && inStakingEra && len(doubleSigners) > 0 {
if err := applySlashes(chain, header, state, doubleSigners); err != nil { if err := applySlashes(chain, header, state, doubleSigners); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
} else if len(doubleSigners) > 0 { } else if len(doubleSigners) > 0 {
return nil, nil, errors.New("slashes proposed in non-beacon chain or non-staking epoch") return nil, nil, nil, errors.New("slashes proposed in non-beacon chain or non-staking epoch")
} }
// ViewID setting needs to happen after commig sig reward logic for pipelining reason. // ViewID setting needs to happen after commig sig reward logic for pipelining reason.
@ -350,9 +350,36 @@ func (e *engineImpl) Finalize(
remainderOne, remainderOne,
) )
} }
// **** clear stale delegations
// must be done after slashing and paying out rewards
// to avoid conflicts with snapshots
// any nil (un)delegations are eligible to be removed
// bulk pruning happens at the first block of the NoNilDelegationsEpoch + 1
// and each epoch thereafter
delegationsToRemove := make(map[common.Address][]common.Address, 0)
if shouldPruneStaleStakingData(chain, header) {
startTime := time.Now()
// this will modify wrappers in-situ, which will be used by UpdateValidatorSnapshots
// which occurs in the next epoch at the second to last block
err = pruneStaleStakingData(chain, header, state, delegationsToRemove)
if err != nil {
utils.Logger().Error().AnErr("err", err).
Uint64("blockNum", header.Number().Uint64()).
Uint64("epoch", header.Epoch().Uint64()).
Msg("pruneStaleStakingData error")
return nil, nil, nil, err
}
utils.Logger().Info().
Int64("elapsed time", time.Since(startTime).Milliseconds()).
Uint64("blockNum", header.Number().Uint64()).
Uint64("epoch", header.Epoch().Uint64()).
Msg("pruneStaleStakingData")
}
// Finalize the state root // Finalize the state root
header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch()))) header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch())))
return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), delegationsToRemove, payout, nil
} }
// Withdraw unlocked tokens to the delegators' accounts // Withdraw unlocked tokens to the delegators' accounts
@ -409,6 +436,86 @@ func IsCommitteeSelectionBlock(chain engine.ChainReader, header *block.Header) b
return isBeaconChain && header.IsLastBlockInEpoch() && inPreStakingEra return isBeaconChain && header.IsLastBlockInEpoch() && inPreStakingEra
} }
// shouldPruneStaleStakingData checks that all of the following are true
// (1) we are in the beacon chain
// (2) it is the first block of the epoch
// (3) the chain is hard forked to no nil delegations epoch
func shouldPruneStaleStakingData(
chain engine.ChainReader,
header *block.Header,
) bool {
firstBlockInEpoch := false
// if not first epoch
if header.Epoch().Cmp(common.Big0) > 0 {
// calculate the last block of prior epoch
targetEpoch := new(big.Int).Sub(header.Epoch(), common.Big1)
lastBlockNumber := shard.Schedule.EpochLastBlock(targetEpoch.Uint64())
// add 1 to it
firstBlockInEpoch = header.Number().Uint64() == lastBlockNumber+1
} else {
// otherwise gensis block
firstBlockInEpoch = header.Number().Cmp(common.Big0) == 0
}
return header.ShardID() == shard.BeaconChainShardID &&
firstBlockInEpoch &&
chain.Config().IsNoNilDelegations(header.Epoch())
}
// pruneStaleStakingData prunes any stale staking data
// must be called only if shouldPruneStaleStakingData is true
// here and not in staking package to avoid import cycle for state
func pruneStaleStakingData(
chain engine.ChainReader,
header *block.Header,
state *state.DB,
delegationsToRemove map[common.Address][]common.Address,
) error {
validators, err := chain.ReadValidatorList()
if err != nil {
return err
}
for _, validator := range validators {
wrapper, err := state.ValidatorWrapper(validator, true, false)
if err != nil {
return errors.New(
"[pruneStaleStakingData] failed to get validator from state to finalize",
)
}
delegationsFinal := wrapper.Delegations[:0]
for i := range wrapper.Delegations {
delegation := &wrapper.Delegations[i]
shouldRemove := i != 0 && // never remove the (inactive) validator
len(delegation.Undelegations) == 0 &&
delegation.Amount.Cmp(common.Big0) == 0 &&
delegation.Reward.Cmp(common.Big0) == 0
if !shouldRemove {
// append it to final delegations
delegationsFinal = append(
delegationsFinal,
*delegation,
)
} else {
// in this delegator's delegationIndexes, remove the one which has this validatorAddress
// since a validatorAddress is enough information to uniquely identify the delegationIndex
delegationsToRemove[delegation.DelegatorAddress] = append(
delegationsToRemove[delegation.DelegatorAddress],
wrapper.Address,
)
}
}
if len(wrapper.Delegations) != len(delegationsFinal) {
utils.Logger().Info().
Str("ValidatorAddress", wrapper.Address.Hex()).
Uint64("epoch", header.Epoch().Uint64()).
Int("count", len(wrapper.Delegations)-len(delegationsFinal)).
Msg("pruneStaleStakingData pruned count")
}
// now re-assign the delegations
wrapper.Delegations = delegationsFinal
}
return nil
}
func setElectionEpochAndMinFee(chain engine.ChainReader, header *block.Header, state *state.DB, config *params.ChainConfig) error { func setElectionEpochAndMinFee(chain engine.ChainReader, header *block.Header, state *state.DB, config *params.ChainConfig) error {
newShardState, err := header.GetShardState() newShardState, err := header.GetShardState()
if err != nil { if err != nil {

@ -1,29 +1,38 @@
package chain package chain
import ( import (
"bytes"
"fmt" "fmt"
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
bls_core "github.com/harmony-one/bls/ffi/go/bls" bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
blockfactory "github.com/harmony-one/harmony/block/factory" blockfactory "github.com/harmony-one/harmony/block/factory"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/consensus/engine" "github.com/harmony-one/harmony/consensus/engine"
consensus_sig "github.com/harmony-one/harmony/consensus/signature" consensus_sig "github.com/harmony-one/harmony/consensus/signature"
"github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/effective" "github.com/harmony-one/harmony/staking/effective"
"github.com/harmony-one/harmony/staking/slash" "github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
types2 "github.com/harmony-one/harmony/staking/types" types2 "github.com/harmony-one/harmony/staking/types"
staketest "github.com/harmony-one/harmony/staking/types/test"
"github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/state/snapshot" "github.com/harmony-one/harmony/core/state/snapshot"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/params"
) )
@ -35,6 +44,10 @@ var (
thousandKOnes = new(big.Int).Mul(big.NewInt(1000000), bigOne) thousandKOnes = new(big.Int).Mul(big.NewInt(1000000), bigOne)
) )
type fakeReader struct {
core.FakeChainReader
}
const ( const (
// validator creation parameters // validator creation parameters
doubleSignShardID = 0 doubleSignShardID = 0
@ -305,6 +318,90 @@ func makeFakeBlockChain() *fakeBlockChain {
} }
} }
func makeTestAddr(item interface{}) common.Address {
s := fmt.Sprintf("harmony-one-%v", item)
return common.BytesToAddress([]byte(s))
}
var (
validator1 = makeTestAddr("validator1")
validator2 = makeTestAddr("validator2")
delegator1 = makeTestAddr("delegator1")
delegator2 = makeTestAddr("delegator2")
delegator3 = makeTestAddr("delegator3")
)
var (
defaultDesc = staking.Description{
Name: "SuperHero",
Identity: "YouWouldNotKnow",
Website: "Secret Website",
SecurityContact: "LicenseToKill",
Details: "blah blah blah",
}
defaultCommissionRates = staking.CommissionRates{
Rate: numeric.NewDecWithPrec(1, 1),
MaxRate: numeric.NewDecWithPrec(9, 1),
MaxChangeRate: numeric.NewDecWithPrec(5, 1),
}
)
func (cr *fakeReader) ReadValidatorList() ([]common.Address, error) {
return []common.Address{validator1, validator2}, nil
}
func getDatabase() *state.DB {
database := rawdb.NewMemoryDatabase()
gspec := core.Genesis{Factory: blockfactory.ForTest}
genesis := gspec.MustCommit(database)
chain, _ := core.NewBlockChain(database, nil, nil, nil, nil, nil, vm.Config{}) //doublecheck this
db, _ := chain.StateAt(genesis.Root())
return db
}
func generateBLSKeyAndSig() (bls.SerializedPublicKey, bls.SerializedSignature) {
blsPriv := bls.RandPrivateKey()
blsPub := blsPriv.GetPublicKey()
msgHash := hash.Keccak256([]byte(staking.BLSVerificationStr))
sig := blsPriv.SignHash(msgHash)
var shardPub bls.SerializedPublicKey
copy(shardPub[:], blsPub.Serialize())
var shardSig bls.SerializedSignature
copy(shardSig[:], sig.Serialize())
return shardPub, shardSig
}
func sampleWrapper(address common.Address) *staking.ValidatorWrapper {
pub, _ := generateBLSKeyAndSig()
v := staking.Validator{
Address: address,
SlotPubKeys: []bls.SerializedPublicKey{pub},
LastEpochInCommittee: new(big.Int),
MinSelfDelegation: staketest.DefaultMinSelfDel,
MaxTotalDelegation: staketest.DefaultMaxTotalDel,
Commission: staking.Commission{
CommissionRates: defaultCommissionRates,
UpdateHeight: big.NewInt(100),
},
Description: defaultDesc,
CreationHeight: big.NewInt(100),
}
// ds := staking.Delegations{
// staking.NewDelegation(address, big.NewInt(0)),
// }
w := &staking.ValidatorWrapper{
Validator: v,
BlockReward: big.NewInt(0),
}
w.Counters.NumBlocksSigned = common.Big0
w.Counters.NumBlocksToSign = common.Big0
return w
}
func makeBlockForTest(epoch int64, index int) *types.Block { func makeBlockForTest(epoch int64, index int) *types.Block {
h := blockfactory.NewTestHeader() h := blockfactory.NewTestHeader()
@ -395,3 +492,103 @@ func makeVoteData(kp blsKeyPair, block *types.Block) slash.Vote {
Signature: kp.Sign(block), Signature: kp.Sign(block),
} }
} }
func TestPruneStaleStakingData(t *testing.T) {
blockFactory := blockfactory.ForTest
header := blockFactory.NewHeader(common.Big0) // epoch
chain := fakeReader{core.FakeChainReader{InternalConfig: params.LocalnetChainConfig}}
db := getDatabase()
// now make the two wrappers and store them
wrapper := sampleWrapper(validator1)
wrapper.Status = effective.Inactive
wrapper.Delegations = staking.Delegations{
staking.NewDelegation(wrapper.Address, big.NewInt(0)),
staking.NewDelegation(delegator1, big.NewInt(0)),
staking.NewDelegation(delegator2, big.NewInt(0)),
staking.NewDelegation(delegator3, new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100))),
}
if err := wrapper.Delegations[3].Undelegate(
big.NewInt(2), new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)), nil,
); err != nil {
t.Fatalf("Got error %v", err)
}
if wrapper.Delegations[3].Amount.Cmp(common.Big0) != 0 {
t.Fatalf("Expected 0 delegation but got %v", wrapper.Delegations[3].Amount)
}
if err := db.UpdateValidatorWrapper(wrapper.Address, wrapper); err != nil {
t.Fatalf("Got error %v", err)
}
wrapper = sampleWrapper(validator2)
wrapper.Status = effective.Active
wrapper.Delegations = staking.Delegations{
staking.NewDelegation(wrapper.Address, new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(10000))),
staking.NewDelegation(delegator1, new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100))),
staking.NewDelegation(delegator2, big.NewInt(0)),
staking.NewDelegation(delegator3, big.NewInt(0)),
staking.NewDelegation(validator1, big.NewInt(0)),
}
wrapper.Delegations[3].Reward = common.Big257
if err := db.UpdateValidatorWrapper(wrapper.Address, wrapper); err != nil {
t.Fatalf("Got error %v", err)
}
// we expect
// (1) validator1 to show up with validator2 only (and not validator1 where the delegation is 0)
// (2) delegator1 to show up with validator1 only (validator2 has amount)
// (3) delegator2 to show up with both validator1 and validator2
// (4) delegator3 to show up with neither validator1 (undelegation) nor validator2 (reward)
delegationsToRemove := make(map[common.Address][]common.Address, 0)
if err := pruneStaleStakingData(&chain, header, db, delegationsToRemove); err != nil {
t.Fatalf("Got error %v", err)
}
if toRemove, ok := delegationsToRemove[validator1]; ok {
if len(toRemove) != 1 {
t.Errorf("Unexpected # of removals for validator1 %d", len(toRemove))
}
if len(toRemove) > 0 {
for _, validatorAddress := range toRemove {
if bytes.Equal(validatorAddress.Bytes(), validator1.Bytes()) {
t.Errorf("Found validator1 being removed from validator1's delegations")
}
}
}
}
if toRemove, ok := delegationsToRemove[delegator1]; ok {
if len(toRemove) != 1 {
t.Errorf("Unexpected # of removals for delegator1 %d", len(toRemove))
}
if len(toRemove) > 0 {
for _, validatorAddress := range toRemove {
if !bytes.Equal(validatorAddress.Bytes(), validator1.Bytes()) {
t.Errorf("Unexpected removal for delegator1; validator1 %s, validator2 %s, validatorAddress %s",
validator1.Hex(),
validator2.Hex(),
validatorAddress.Hex(),
)
}
}
}
}
if toRemove, ok := delegationsToRemove[delegator2]; ok {
if len(toRemove) != 2 {
t.Errorf("Unexpected # of removals for delegator2 %d", len(toRemove))
}
if len(toRemove) > 0 {
for _, validatorAddress := range toRemove {
if !(bytes.Equal(validatorAddress.Bytes(), validator1.Bytes()) ||
bytes.Equal(validatorAddress.Bytes(), validator2.Bytes())) {
t.Errorf("Unexpected removal for delegator2; validator1 %s, validator2 %s, validatorAddress %s",
validator1.Hex(),
validator2.Hex(),
validatorAddress.Hex(),
)
}
}
}
}
if toRemove, ok := delegationsToRemove[delegator3]; ok {
if len(toRemove) != 0 {
t.Errorf("Unexpected # of removals for delegator3 %d", len(toRemove))
}
}
}

@ -270,7 +270,8 @@ func AccumulateRewardsAndCountSigs(
// Handle rewards on pre-aggregated rewards era. // Handle rewards on pre-aggregated rewards era.
if !bc.Config().IsAggregatedRewardEpoch(header.Epoch()) { if !bc.Config().IsAggregatedRewardEpoch(header.Epoch()) {
return distributeRewardBeforeAggregateEpoch(bc, state, header, beaconChain, defaultReward, sigsReady) reader, err := distributeRewardBeforeAggregateEpoch(bc, state, header, beaconChain, defaultReward, sigsReady)
return numeric.ZeroDec(), reader, err
} }
// Aggregated Rewards Era: Rewards are aggregated every 64 blocks. // Aggregated Rewards Era: Rewards are aggregated every 64 blocks.
@ -285,7 +286,8 @@ func AccumulateRewardsAndCountSigs(
return numeric.ZeroDec(), network.EmptyPayout, nil return numeric.ZeroDec(), network.EmptyPayout, nil
} }
return distributeRewardAfterAggregateEpoch(bc, state, header, beaconChain, defaultReward) _, reader, err := distributeRewardAfterAggregateEpoch(bc, state, header, beaconChain, defaultReward)
return numeric.ZeroDec(), reader, err
} }
func waitForCommitSigs(sigsReady chan bool) error { func waitForCommitSigs(sigsReady chan bool) error {
@ -405,21 +407,39 @@ func distributeRewardAfterAggregateEpoch(bc engine.ChainReader, state *state.DB,
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return numeric.ZeroDec(), network.EmptyPayout, err
} }
if err := state.AddReward(snapshot.Validator, due, shares); err != nil { if err := state.AddReward(
snapshot.Validator,
due,
shares,
// epoch prior to no nil delegations
// block 32767 -> snapshot saved, rewards paid
// in no nil delegations epoch
// block 1 -> rewards not paid (unless schedule changes),
// and delegations pruned afterwards
bc.Config().IsNoNilDelegations(header.Epoch()),
); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return numeric.ZeroDec(), network.EmptyPayout, err
} }
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTimeLocal).Milliseconds()).Msg("After Chain Reward (AddReward)")
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("After Chain Reward") utils.Logger().Debug().Int64("elapsed time", time.Since(startTimeLocal).Milliseconds()).Msg("After Chain Reward (AddReward)")
utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("After Chain Reward")
// remainingReward needs to be multipled with the number of crosslinks across all shards // remainingReward needs to be multipled with the number of crosslinks across all shards
return remainingReward.MulInt(big.NewInt(int64(len(allCrossLinks)))), network.NewStakingEraRewardForRound( return remainingReward.MulInt(big.NewInt(int64(len(allCrossLinks)))), network.NewStakingEraRewardForRound(
newRewards, payouts, newRewards, payouts,
), nil ), nil
} }
func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB, header *block.Header, beaconChain engine.ChainReader, func distributeRewardBeforeAggregateEpoch(
defaultReward numeric.Dec, sigsReady chan bool) (numeric.Dec, reward.Reader, error) { bc engine.ChainReader,
state *state.DB,
header *block.Header,
beaconChain engine.ChainReader,
defaultReward numeric.Dec,
sigsReady chan bool,
) (reward.Reader, error) {
newRewards, payouts := newRewards, payouts :=
big.NewInt(0), []reward.Payout{} big.NewInt(0), []reward.Payout{}
@ -429,9 +449,9 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
startTime := time.Now() startTime := time.Now()
crossLinks := types.CrossLinks{} crossLinks := types.CrossLinks{}
if err := rlp.DecodeBytes(cxLinks, &crossLinks); err != nil { if err := rlp.DecodeBytes(cxLinks, &crossLinks); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Decode Cross Links") utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("Decode Cross Links")
startTime = time.Now() startTime = time.Now()
for i := range crossLinks { for i := range crossLinks {
@ -439,7 +459,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payables, _, err := processOneCrossLink(bc, state, cxLink, defaultReward, i) payables, _, err := processOneCrossLink(bc, state, cxLink, defaultReward, i)
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
allPayables = append(allPayables, payables...) allPayables = append(allPayables, payables...)
@ -473,17 +493,17 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payable.EcdsaAddress, payable.EcdsaAddress,
) )
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
due := resultsHandle[bucket][payThem].payout due := resultsHandle[bucket][payThem].payout
newRewards.Add(newRewards, due) newRewards.Add(newRewards, due)
shares, err := lookupDelegatorShares(snapshot) shares, err := lookupDelegatorShares(snapshot)
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
if err := state.AddReward(snapshot.Validator, due, shares); err != nil { if err := state.AddReward(snapshot.Validator, due, shares, bc.Config().IsNoNilDelegations(header.Epoch())); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
payouts = append(payouts, reward.Payout{ payouts = append(payouts, reward.Payout{
Addr: payable.EcdsaAddress, Addr: payable.EcdsaAddress,
@ -492,21 +512,21 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
}) })
} }
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTimeLocal).Milliseconds()).Msg("Shard Chain Reward (AddReward)") utils.Logger().Debug().Int64("elapsed time", time.Since(startTimeLocal).Milliseconds()).Msg("Shard Chain Reward (AddReward)")
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Shard Chain Reward") utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("Shard Chain Reward")
} }
// Block here until the commit sigs are ready or timeout. // Block here until the commit sigs are ready or timeout.
// sigsReady signal indicates that the commit sigs are already populated in the header object. // sigsReady signal indicates that the commit sigs are already populated in the header object.
if err := waitForCommitSigs(sigsReady); err != nil { if err := waitForCommitSigs(sigsReady); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
startTime := time.Now() startTime := time.Now()
// Take care of my own beacon chain committee, _ is missing, for slashing // Take care of my own beacon chain committee, _ is missing, for slashing
parentE, members, payable, missing, err := ballotResultBeaconchain(beaconChain, header) parentE, members, payable, missing, err := ballotResultBeaconchain(beaconChain, header)
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, errors.Wrapf(err, "shard 0 block %d reward error with bitmap %x", header.Number(), header.LastCommitBitmap()) return network.EmptyPayout, errors.Wrapf(err, "shard 0 block %d reward error with bitmap %x", header.Number(), header.LastCommitBitmap())
} }
subComm := shard.Committee{ShardID: shard.BeaconChainShardID, Slots: members} subComm := shard.Committee{ShardID: shard.BeaconChainShardID, Slots: members}
@ -517,13 +537,13 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payable, payable,
missing, missing,
); err != nil { ); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
votingPower, err := lookupVotingPower( votingPower, err := lookupVotingPower(
parentE, &subComm, parentE, &subComm,
) )
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
allSignersShare := numeric.ZeroDec() allSignersShare := numeric.ZeroDec()
@ -540,7 +560,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
if !voter.IsHarmonyNode { if !voter.IsHarmonyNode {
snapshot, err := bc.ReadValidatorSnapshot(voter.EarningAccount) snapshot, err := bc.ReadValidatorSnapshot(voter.EarningAccount)
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
due := defaultReward.Mul( due := defaultReward.Mul(
voter.OverallPercent.Quo(allSignersShare), voter.OverallPercent.Quo(allSignersShare),
@ -549,10 +569,10 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
shares, err := lookupDelegatorShares(snapshot) shares, err := lookupDelegatorShares(snapshot)
if err != nil { if err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
if err := state.AddReward(snapshot.Validator, due, shares); err != nil { if err := state.AddReward(snapshot.Validator, due, shares, bc.Config().IsNoNilDelegations(header.Epoch())); err != nil {
return numeric.ZeroDec(), network.EmptyPayout, err return network.EmptyPayout, err
} }
payouts = append(payouts, reward.Payout{ payouts = append(payouts, reward.Payout{
Addr: voter.EarningAccount, Addr: voter.EarningAccount,
@ -561,9 +581,9 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
}) })
} }
} }
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Beacon Chain Reward") utils.Logger().Debug().Int64("elapsed time", time.Since(startTime).Milliseconds()).Msg("Beacon Chain Reward")
return numeric.ZeroDec(), network.NewStakingEraRewardForRound( return network.NewStakingEraRewardForRound(
newRewards, payouts, newRewards, payouts,
), nil ), nil
} }

@ -74,6 +74,7 @@ var (
FeeCollectEpoch: big.NewInt(1535), // 2023-07-20 05:51:07+00:00 FeeCollectEpoch: big.NewInt(1535), // 2023-07-20 05:51:07+00:00
ValidatorCodeFixEpoch: big.NewInt(1535), // 2023-07-20 05:51:07+00:00 ValidatorCodeFixEpoch: big.NewInt(1535), // 2023-07-20 05:51:07+00:00
HIP30Epoch: big.NewInt(1673), // 2023-11-02 17:30:00+00:00 HIP30Epoch: big.NewInt(1673), // 2023-11-02 17:30:00+00:00
NoNilDelegationsEpoch: EpochTBD,
BlockGas30MEpoch: big.NewInt(1673), // 2023-11-02 17:30:00+00:00 BlockGas30MEpoch: big.NewInt(1673), // 2023-11-02 17:30:00+00:00
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -118,6 +119,7 @@ var (
FeeCollectEpoch: big.NewInt(1296), // 2023-04-28 07:14:20+00:00 FeeCollectEpoch: big.NewInt(1296), // 2023-04-28 07:14:20+00:00
ValidatorCodeFixEpoch: big.NewInt(1296), // 2023-04-28 07:14:20+00:00 ValidatorCodeFixEpoch: big.NewInt(1296), // 2023-04-28 07:14:20+00:00
HIP30Epoch: big.NewInt(2176), // 2023-10-12 10:00:00+00:00 HIP30Epoch: big.NewInt(2176), // 2023-10-12 10:00:00+00:00
NoNilDelegationsEpoch: EpochTBD,
BlockGas30MEpoch: big.NewInt(2176), // 2023-10-12 10:00:00+00:00 BlockGas30MEpoch: big.NewInt(2176), // 2023-10-12 10:00:00+00:00
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -162,6 +164,7 @@ var (
FeeCollectEpoch: EpochTBD, FeeCollectEpoch: EpochTBD,
ValidatorCodeFixEpoch: EpochTBD, ValidatorCodeFixEpoch: EpochTBD,
HIP30Epoch: EpochTBD, HIP30Epoch: EpochTBD,
NoNilDelegationsEpoch: EpochTBD,
BlockGas30MEpoch: big.NewInt(0), BlockGas30MEpoch: big.NewInt(0),
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -208,6 +211,7 @@ var (
ValidatorCodeFixEpoch: big.NewInt(5), ValidatorCodeFixEpoch: big.NewInt(5),
HIP30Epoch: big.NewInt(7), HIP30Epoch: big.NewInt(7),
BlockGas30MEpoch: big.NewInt(7), BlockGas30MEpoch: big.NewInt(7),
NoNilDelegationsEpoch: EpochTBD,
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -252,6 +256,7 @@ var (
LeaderRotationExternalValidatorsEpoch: EpochTBD, LeaderRotationExternalValidatorsEpoch: EpochTBD,
ValidatorCodeFixEpoch: EpochTBD, ValidatorCodeFixEpoch: EpochTBD,
HIP30Epoch: EpochTBD, HIP30Epoch: EpochTBD,
NoNilDelegationsEpoch: big.NewInt(2),
BlockGas30MEpoch: big.NewInt(0), BlockGas30MEpoch: big.NewInt(0),
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -296,6 +301,7 @@ var (
FeeCollectEpoch: big.NewInt(2), FeeCollectEpoch: big.NewInt(2),
ValidatorCodeFixEpoch: big.NewInt(2), ValidatorCodeFixEpoch: big.NewInt(2),
HIP30Epoch: EpochTBD, HIP30Epoch: EpochTBD,
NoNilDelegationsEpoch: big.NewInt(2),
BlockGas30MEpoch: big.NewInt(0), BlockGas30MEpoch: big.NewInt(0),
MaxRateEpoch: EpochTBD, MaxRateEpoch: EpochTBD,
} }
@ -342,7 +348,8 @@ var (
big.NewInt(0), // FeeCollectEpoch big.NewInt(0), // FeeCollectEpoch
big.NewInt(0), // ValidatorCodeFixEpoch big.NewInt(0), // ValidatorCodeFixEpoch
big.NewInt(0), // BlockGas30M big.NewInt(0), // BlockGas30M
big.NewInt(0), // BlockGas30M big.NewInt(0), // HIP30Epoch
big.NewInt(0), // NoNilDelegationsEpoch
big.NewInt(0), // MaxRateEpoch big.NewInt(0), // MaxRateEpoch
} }
@ -388,6 +395,7 @@ var (
big.NewInt(0), // FeeCollectEpoch big.NewInt(0), // FeeCollectEpoch
big.NewInt(0), // ValidatorCodeFixEpoch big.NewInt(0), // ValidatorCodeFixEpoch
big.NewInt(0), // HIP30Epoch big.NewInt(0), // HIP30Epoch
big.NewInt(0), // NoNilDelegationsEpoch
big.NewInt(0), // BlockGas30M big.NewInt(0), // BlockGas30M
big.NewInt(0), // MaxRateEpoch big.NewInt(0), // MaxRateEpoch
} }
@ -529,6 +537,9 @@ type ChainConfig struct {
// AllowlistEpoch is the first epoch to support allowlist of HIP18 // AllowlistEpoch is the first epoch to support allowlist of HIP18
AllowlistEpoch *big.Int AllowlistEpoch *big.Int
// The first epoch at the end of which stale delegations are removed
NoNilDelegationsEpoch *big.Int `json:"no-nil-delegations-epoch,omitempty"`
LeaderRotationInternalValidatorsEpoch *big.Int `json:"leader-rotation-internal-validators,omitempty"` LeaderRotationInternalValidatorsEpoch *big.Int `json:"leader-rotation-internal-validators,omitempty"`
LeaderRotationExternalValidatorsEpoch *big.Int `json:"leader-rotation-external-validators,omitempty"` LeaderRotationExternalValidatorsEpoch *big.Int `json:"leader-rotation-external-validators,omitempty"`
@ -562,7 +573,19 @@ type ChainConfig struct {
// String implements the fmt.Stringer interface. // String implements the fmt.Stringer interface.
func (c *ChainConfig) String() string { func (c *ChainConfig) String() string {
return fmt.Sprintf("{ChainID: %v EthCompatibleChainID: %v EIP155: %v CrossTx: %v Staking: %v CrossLink: %v ReceiptLog: %v SHA3Epoch: %v StakingPrecompileEpoch: %v ChainIdFixEpoch: %v CrossShardXferPrecompileEpoch: %v}", // use string1 + string2 here instead of concatening in the end
return fmt.Sprintf("{ChainID: %v "+
"EthCompatibleChainID: %v "+
"EIP155: %v "+
"CrossTx: %v "+
"Staking: %v "+
"CrossLink: %v "+
"ReceiptLog: %v "+
"SHA3Epoch: %v "+
"StakingPrecompileEpoch: %v "+
"ChainIdFixEpoch: %v "+
"CrossShardXferPrecompileEpoch: %v "+
"NoNilDelegationsEpoch: %v}",
c.ChainID, c.ChainID,
c.EthCompatibleChainID, c.EthCompatibleChainID,
c.EIP155Epoch, c.EIP155Epoch,
@ -574,6 +597,7 @@ func (c *ChainConfig) String() string {
c.StakingPrecompileEpoch, c.StakingPrecompileEpoch,
c.ChainIdFixEpoch, c.ChainIdFixEpoch,
c.CrossShardXferPrecompileEpoch, c.CrossShardXferPrecompileEpoch,
c.NoNilDelegationsEpoch,
) )
} }
@ -767,12 +791,18 @@ func (c *ChainConfig) IsHIP6And8Epoch(epoch *big.Int) bool {
return isForked(c.HIP6And8Epoch, epoch) return isForked(c.HIP6And8Epoch, epoch)
} }
// IsStakingPrecompileEpoch determines whether staking // IsStakingPrecompile determines whether staking
// precompiles are available in the EVM // precompiles are available in the EVM
func (c *ChainConfig) IsStakingPrecompile(epoch *big.Int) bool { func (c *ChainConfig) IsStakingPrecompile(epoch *big.Int) bool {
return isForked(c.StakingPrecompileEpoch, epoch) return isForked(c.StakingPrecompileEpoch, epoch)
} }
// IsNoNilDelegations determines whether to clear
// nil delegations regularly (and of course also once)
func (c *ChainConfig) IsNoNilDelegations(epoch *big.Int) bool {
return isForked(c.NoNilDelegationsEpoch, epoch)
}
// IsCrossShardXferPrecompile determines whether the // IsCrossShardXferPrecompile determines whether the
// Cross Shard Transfer Precompile is available in the EVM // Cross Shard Transfer Precompile is available in the EVM
func (c *ChainConfig) IsCrossShardXferPrecompile(epoch *big.Int) bool { func (c *ChainConfig) IsCrossShardXferPrecompile(epoch *big.Int) bool {
@ -882,6 +912,7 @@ type Rules struct {
// eip-155 chain id fix // eip-155 chain id fix
IsChainIdFix bool IsChainIdFix bool
IsValidatorCodeFix bool IsValidatorCodeFix bool
IsNoNilDelegations bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
@ -907,5 +938,6 @@ func (c *ChainConfig) Rules(epoch *big.Int) Rules {
IsCrossShardXferPrecompile: c.IsCrossShardXferPrecompile(epoch), IsCrossShardXferPrecompile: c.IsCrossShardXferPrecompile(epoch),
IsChainIdFix: c.IsChainIdFix(epoch), IsChainIdFix: c.IsChainIdFix(epoch),
IsValidatorCodeFix: c.IsValidatorCodeFix(epoch), IsValidatorCodeFix: c.IsValidatorCodeFix(epoch),
IsNoNilDelegations: c.IsNoNilDelegations(epoch),
} }
} }

@ -32,20 +32,21 @@ import (
// environment is the worker's current environment and holds all of the current state information. // environment is the worker's current environment and holds all of the current state information.
type environment struct { type environment struct {
signer types.Signer signer types.Signer
ethSigner types.Signer ethSigner types.Signer
state *state.DB // apply state changes here state *state.DB // apply state changes here
gasPool *core.GasPool // available gas used to pack transactions gasPool *core.GasPool // available gas used to pack transactions
header *block.Header header *block.Header
txs []*types.Transaction txs []*types.Transaction
stakingTxs []*staking.StakingTransaction stakingTxs []*staking.StakingTransaction
receipts []*types.Receipt receipts []*types.Receipt
logs []*types.Log logs []*types.Log
reward reward.Reader reward reward.Reader
outcxs []*types.CXReceipt // cross shard transaction receipts (source shard) outcxs []*types.CXReceipt // cross shard transaction receipts (source shard)
incxs []*types.CXReceiptsProof // cross shard receipts and its proof (desitinatin shard) incxs []*types.CXReceiptsProof // cross shard receipts and its proof (desitinatin shard)
slashes slash.Records slashes slash.Records
stakeMsgs []staking.StakeMsg stakeMsgs []staking.StakeMsg
delegationsToRemove map[common.Address][]common.Address
} }
func (env *environment) CurrentHeader() *block.Header { func (env *environment) CurrentHeader() *block.Header {
@ -407,13 +408,14 @@ func makeEnvironment(chain core.BlockChain, parent *block.Header, header *block.
// GetCurrentResult gets the current block processing result. // GetCurrentResult gets the current block processing result.
func (w *Worker) GetCurrentResult() *core.ProcessorResult { func (w *Worker) GetCurrentResult() *core.ProcessorResult {
return &core.ProcessorResult{ return &core.ProcessorResult{
Receipts: w.current.receipts, Receipts: w.current.receipts,
CxReceipts: w.current.outcxs, CxReceipts: w.current.outcxs,
Logs: w.current.logs, Logs: w.current.logs,
UsedGas: w.current.header.GasUsed(), UsedGas: w.current.header.GasUsed(),
Reward: w.current.reward, Reward: w.current.reward,
State: w.current.state, State: w.current.state,
StakeMsgs: w.current.stakeMsgs, StakeMsgs: w.current.stakeMsgs,
DelegationsToRemove: w.current.delegationsToRemove,
} }
} }
@ -615,7 +617,7 @@ func (w *Worker) FinalizeNewBlock(
} }
}() }()
block, payout, err := w.chain.Engine().Finalize( block, delegationsToRemove, payout, err := w.chain.Engine().Finalize(
w.chain, w.chain,
w.beacon, w.beacon,
copyHeader, state, w.current.txs, w.current.receipts, copyHeader, state, w.current.txs, w.current.receipts,
@ -626,6 +628,7 @@ func (w *Worker) FinalizeNewBlock(
return nil, errors.Wrapf(err, "cannot finalize block") return nil, errors.Wrapf(err, "cannot finalize block")
} }
w.current.reward = payout w.current.reward = payout
w.current.delegationsToRemove = delegationsToRemove
return block, nil return block, nil
} }

@ -2,18 +2,21 @@ package types
import ( import (
"encoding/json" "encoding/json"
"errors" //"errors"
"fmt"
"math/big" "math/big"
"sort" "sort"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/crypto/hash" "github.com/harmony-one/harmony/crypto/hash"
common2 "github.com/harmony-one/harmony/internal/common" common2 "github.com/harmony-one/harmony/internal/common"
"github.com/pkg/errors"
) )
var ( var (
errInsufficientBalance = errors.New("insufficient balance to undelegate") errInsufficientBalance = errors.New("insufficient balance to undelegate")
errInvalidAmount = errors.New("invalid amount, must be positive") errInvalidAmount = errors.New("invalid amount, must be positive")
ErrUndelegationRemaining = errors.New("remaining delegation must be 0 or >= 100 ONE")
) )
const ( const (
@ -120,13 +123,25 @@ func NewDelegation(delegatorAddr common.Address,
} }
// Undelegate - append entry to the undelegation // Undelegate - append entry to the undelegation
func (d *Delegation) Undelegate(epoch *big.Int, amt *big.Int) error { func (d *Delegation) Undelegate(
epoch *big.Int,
amt *big.Int,
minimumRemainingDelegation *big.Int,
) error {
if amt.Sign() <= 0 { if amt.Sign() <= 0 {
return errInvalidAmount return errInvalidAmount
} }
if d.Amount.Cmp(amt) < 0 { if d.Amount.Cmp(amt) < 0 {
return errInsufficientBalance return errInsufficientBalance
} }
if minimumRemainingDelegation != nil {
finalAmount := big.NewInt(0).Sub(d.Amount, amt)
if finalAmount.Cmp(minimumRemainingDelegation) < 0 && finalAmount.Cmp(common.Big0) != 0 {
return errors.Wrapf(ErrUndelegationRemaining,
fmt.Sprintf("Minimum: %d, Remaining: %d", minimumRemainingDelegation, finalAmount),
)
}
}
d.Amount.Sub(d.Amount, amt) d.Amount.Sub(d.Amount, amt)
exist := false exist := false

@ -19,7 +19,7 @@ var (
func TestUndelegate(t *testing.T) { func TestUndelegate(t *testing.T) {
epoch1 := big.NewInt(10) epoch1 := big.NewInt(10)
amount1 := big.NewInt(1000) amount1 := big.NewInt(1000)
delegation.Undelegate(epoch1, amount1) delegation.Undelegate(epoch1, amount1, nil)
// check the undelegation's Amount // check the undelegation's Amount
if delegation.Undelegations[0].Amount.Cmp(amount1) != 0 { if delegation.Undelegations[0].Amount.Cmp(amount1) != 0 {
@ -32,7 +32,7 @@ func TestUndelegate(t *testing.T) {
epoch2 := big.NewInt(12) epoch2 := big.NewInt(12)
amount2 := big.NewInt(2000) amount2 := big.NewInt(2000)
delegation.Undelegate(epoch2, amount2) delegation.Undelegate(epoch2, amount2, nil)
// check the number of undelegations // check the number of undelegations
if len(delegation.Undelegations) != 2 { if len(delegation.Undelegations) != 2 {
@ -54,7 +54,7 @@ func TestDeleteEntry(t *testing.T) {
// Undelegations[]: 1000, 2000, 3000 // Undelegations[]: 1000, 2000, 3000
epoch3 := big.NewInt(15) epoch3 := big.NewInt(15)
amount3 := big.NewInt(3000) amount3 := big.NewInt(3000)
delegation.Undelegate(epoch3, amount3) delegation.Undelegate(epoch3, amount3, nil)
// delete the second undelegation entry // delete the second undelegation entry
// Undelegations[]: 1000, 3000 // Undelegations[]: 1000, 3000
@ -73,7 +73,7 @@ func TestUnlockedLastEpochInCommittee(t *testing.T) {
epoch4 := big.NewInt(21) epoch4 := big.NewInt(21)
amount4 := big.NewInt(4000) amount4 := big.NewInt(4000)
delegation.Undelegate(epoch4, amount4) delegation.Undelegate(epoch4, amount4, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false)
if result.Cmp(big.NewInt(8000)) != 0 { if result.Cmp(big.NewInt(8000)) != 0 {
@ -88,7 +88,7 @@ func TestUnlockedLastEpochInCommitteeFail(t *testing.T) {
epoch4 := big.NewInt(21) epoch4 := big.NewInt(21)
amount4 := big.NewInt(4000) amount4 := big.NewInt(4000)
delegation.Undelegate(epoch4, amount4) delegation.Undelegate(epoch4, amount4, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false)
if result.Cmp(big.NewInt(0)) != 0 { if result.Cmp(big.NewInt(0)) != 0 {
@ -102,7 +102,7 @@ func TestUnlockedFullPeriod(t *testing.T) {
epoch5 := big.NewInt(27) epoch5 := big.NewInt(27)
amount5 := big.NewInt(4000) amount5 := big.NewInt(4000)
delegation.Undelegate(epoch5, amount5) delegation.Undelegate(epoch5, amount5, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false)
if result.Cmp(big.NewInt(4000)) != 0 { if result.Cmp(big.NewInt(4000)) != 0 {
@ -116,7 +116,7 @@ func TestQuickUnlock(t *testing.T) {
epoch7 := big.NewInt(44) epoch7 := big.NewInt(44)
amount7 := big.NewInt(4000) amount7 := big.NewInt(4000)
delegation.Undelegate(epoch7, amount7) delegation.Undelegate(epoch7, amount7, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 0, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 0, false)
if result.Cmp(big.NewInt(4000)) != 0 { if result.Cmp(big.NewInt(4000)) != 0 {
@ -131,7 +131,7 @@ func TestUnlockedFullPeriodFail(t *testing.T) {
epoch5 := big.NewInt(28) epoch5 := big.NewInt(28)
amount5 := big.NewInt(4000) amount5 := big.NewInt(4000)
delegation.Undelegate(epoch5, amount5) delegation.Undelegate(epoch5, amount5, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false)
if result.Cmp(big.NewInt(0)) != 0 { if result.Cmp(big.NewInt(0)) != 0 {
@ -145,7 +145,7 @@ func TestUnlockedPremature(t *testing.T) {
epoch6 := big.NewInt(42) epoch6 := big.NewInt(42)
amount6 := big.NewInt(4000) amount6 := big.NewInt(4000)
delegation.Undelegate(epoch6, amount6) delegation.Undelegate(epoch6, amount6, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, false)
if result.Cmp(big.NewInt(0)) != 0 { if result.Cmp(big.NewInt(0)) != 0 {
@ -159,7 +159,7 @@ func TestNoEarlyUnlock(t *testing.T) {
epoch4 := big.NewInt(21) epoch4 := big.NewInt(21)
amount4 := big.NewInt(4000) amount4 := big.NewInt(4000)
delegation.Undelegate(epoch4, amount4) delegation.Undelegate(epoch4, amount4, nil)
result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, true) result := delegation.RemoveUnlockedUndelegations(curEpoch, lastEpochInCommittee, 7, true)
if result.Cmp(big.NewInt(0)) != 0 { if result.Cmp(big.NewInt(0)) != 0 {

@ -139,7 +139,7 @@ func main() {
fmt.Printf("Time required to calc percentage %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds()) fmt.Printf("Time required to calc percentage %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds())
startTime = time.Now() startTime = time.Now()
statedb.AddReward(validator, big.NewInt(1000), shares) statedb.AddReward(validator, big.NewInt(1000), shares, false)
endTime = time.Now() endTime = time.Now()
fmt.Printf("Time required to reward a validator with %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds()) fmt.Printf("Time required to reward a validator with %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds())

Loading…
Cancel
Save