add engine_test

feature/dev-engine_test
static 1 year ago
parent 90e71609e0
commit d9cb44aaa0
  1. 527
      internal/chain/engine_test.go

@ -1,391 +1,210 @@
package chain
import (
"bytes"
"fmt"
"math/big"
"testing"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
blockfactory "github.com/harmony-one/harmony/block/factory"
"github.com/harmony-one/harmony/consensus/engine"
consensus_sig "github.com/harmony-one/harmony/consensus/signature"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/effective"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types"
types2 "github.com/harmony-one/harmony/staking/types"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/params"
staketest "github.com/harmony-one/harmony/staking/types/test"
)
var (
bigOne = big.NewInt(1e18)
tenKOnes = new(big.Int).Mul(big.NewInt(10000), bigOne)
twentyKOnes = new(big.Int).Mul(big.NewInt(20000), bigOne)
fourtyKOnes = new(big.Int).Mul(big.NewInt(40000), bigOne)
thousandKOnes = new(big.Int).Mul(big.NewInt(1000000), bigOne)
)
const (
// validator creation parameters
doubleSignShardID = 0
doubleSignEpoch = 4
doubleSignBlockNumber = 37
doubleSignViewID = 38
creationHeight = 33
lastEpochInComm = 5
currentEpoch = 5
numShard = 4
numNodePerShard = 5
type fakeReader struct {
core.FakeChainReader
}
offenderShard = doubleSignShardID
offenderShardIndex = 0
)
func makeTestAddr(item interface{}) common.Address {
s := fmt.Sprintf("harmony-one-%v", item)
return common.BytesToAddress([]byte(s))
}
var (
doubleSignBlock1 = makeBlockForTest(doubleSignEpoch, 0)
doubleSignBlock2 = makeBlockForTest(doubleSignEpoch, 1)
validator1 = makeTestAddr("validator1")
validator2 = makeTestAddr("validator2")
delegator1 = makeTestAddr("delegator1")
delegator2 = makeTestAddr("delegator2")
delegator3 = makeTestAddr("delegator3")
)
var (
keyPairs = genKeyPairs(25)
offIndex = offenderShard*numNodePerShard + offenderShardIndex
offAddr = makeTestAddress(offIndex)
offKey = keyPairs[offIndex]
offPub = offKey.Pub()
leaderAddr = makeTestAddress("leader")
)
// Tests that slashing works on the engine level. Since all slashing is
// thoroughly unit tested on `double-sign_test.go`, it just makes sure that
// slashing is applied to the state.
func TestApplySlashing(t *testing.T) {
chain := makeFakeBlockChain()
state := makeTestStateDB()
header := makeFakeHeader()
current := makeDefaultValidatorWrapper()
slashes := slash.Records{makeSlashRecord()}
if err := state.UpdateValidatorWrapper(current.Address, current); err != nil {
t.Error(err)
}
if _, err := state.Commit(true); err != nil {
t.Error(err)
}
// Inital Leader's balance: 0
// Initial Validator's self-delegation: FourtyKOnes
if err := applySlashes(chain, header, state, slashes); err != nil {
t.Error(err)
defaultDesc = staking.Description{
Name: "SuperHero",
Identity: "YouWouldNotKnow",
Website: "Secret Website",
SecurityContact: "LicenseToKill",
Details: "blah blah blah",
}
expDelAmountAfterSlash := twentyKOnes
expRewardToBeneficiary := tenKOnes
if current.Delegations[0].Amount.Cmp(expDelAmountAfterSlash) != 0 {
t.Errorf("Slashing was not applied properly to validator: %v/%v", expDelAmountAfterSlash, current.Delegations[0].Amount)
defaultCommissionRates = staking.CommissionRates{
Rate: numeric.NewDecWithPrec(1, 1),
MaxRate: numeric.NewDecWithPrec(9, 1),
MaxChangeRate: numeric.NewDecWithPrec(5, 1),
}
)
beneficiaryBalanceAfterSlash := state.GetBalance(leaderAddr)
if beneficiaryBalanceAfterSlash.Cmp(expRewardToBeneficiary) != 0 {
t.Errorf("Slashing reward was not added properly to beneficiary: %v/%v", expRewardToBeneficiary, beneficiaryBalanceAfterSlash)
}
func (cr *fakeReader) ReadValidatorList() ([]common.Address, error) {
return []common.Address{validator1, validator2}, nil
}
//
// Make slash record for testing
//
func makeSlashRecord() slash.Record {
return slash.Record{
Evidence: slash.Evidence{
ConflictingVotes: slash.ConflictingVotes{
FirstVote: makeVoteData(offKey, doubleSignBlock1),
SecondVote: makeVoteData(offKey, doubleSignBlock2),
},
Moment: slash.Moment{
Epoch: big.NewInt(doubleSignEpoch),
ShardID: doubleSignShardID,
Height: doubleSignBlockNumber,
ViewID: doubleSignViewID,
},
Offender: offAddr,
},
Reporter: makeTestAddress("reporter"),
}
func getDatabase() *state.DB {
database := rawdb.NewMemoryDatabase()
gspec := core.Genesis{Factory: blockfactory.ForTest}
genesis := gspec.MustCommit(database)
chain, _ := core.NewBlockChain(database, nil, nil, nil, vm.Config{}, nil)
db, _ := chain.StateAt(genesis.Root())
return db
}
//
// Make validator for testing
//
func generateBLSKeyAndSig() (bls.SerializedPublicKey, bls.SerializedSignature) {
blsPriv := bls.RandPrivateKey()
blsPub := blsPriv.GetPublicKey()
msgHash := hash.Keccak256([]byte(staking.BLSVerificationStr))
sig := blsPriv.SignHash(msgHash)
func makeDefaultValidatorWrapper() *staking.ValidatorWrapper {
pubKeys := []bls.SerializedPublicKey{offPub}
v := defaultTestValidator(pubKeys)
var shardPub bls.SerializedPublicKey
copy(shardPub[:], blsPub.Serialize())
ds := staking.Delegations{}
ds = append(ds, staking.Delegation{
DelegatorAddress: offAddr,
Amount: new(big.Int).Set(fourtyKOnes),
})
var shardSig bls.SerializedSignature
copy(shardSig[:], sig.Serialize())
return &staking.ValidatorWrapper{
Validator: v,
Delegations: ds,
}
return shardPub, shardSig
}
func defaultTestValidator(pubKeys []bls.SerializedPublicKey) staking.Validator {
comm := staking.Commission{
CommissionRates: staking.CommissionRates{
Rate: numeric.MustNewDecFromStr("0.167983520183826780"),
MaxRate: numeric.MustNewDecFromStr("0.179184469782137200"),
MaxChangeRate: numeric.MustNewDecFromStr("0.152212761523253600"),
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),
},
UpdateHeight: big.NewInt(10),
}
desc := staking.Description{
Name: "someoneA",
Identity: "someoneB",
Website: "someoneC",
SecurityContact: "someoneD",
Details: "someoneE",
}
return staking.Validator{
Address: offAddr,
SlotPubKeys: pubKeys,
LastEpochInCommittee: big.NewInt(lastEpochInComm),
MinSelfDelegation: new(big.Int).Set(tenKOnes),
MaxTotalDelegation: new(big.Int).Set(thousandKOnes),
Status: effective.Active,
Commission: comm,
Description: desc,
CreationHeight: big.NewInt(creationHeight),
}
}
//
// Make commitee for testing
//
func makeDefaultCommittee() shard.State {
epoch := big.NewInt(doubleSignEpoch)
maker := newShardSlotMaker(keyPairs)
sstate := shard.State{
Epoch: epoch,
Shards: make([]shard.Committee, 0, int(numShard)),
}
for sid := uint32(0); sid != numNodePerShard; sid++ {
sstate.Shards = append(sstate.Shards, makeShardBySlotMaker(sid, maker))
}
return sstate
}
type shardSlotMaker struct {
kps []blsKeyPair
i int
}
func makeShardBySlotMaker(shardID uint32, maker shardSlotMaker) shard.Committee {
cmt := shard.Committee{
ShardID: shardID,
Slots: make(shard.SlotList, 0, numNodePerShard),
}
for nid := 0; nid != numNodePerShard; nid++ {
cmt.Slots = append(cmt.Slots, maker.makeSlot())
}
return cmt
}
func newShardSlotMaker(kps []blsKeyPair) shardSlotMaker {
return shardSlotMaker{kps, 0}
}
func (maker *shardSlotMaker) makeSlot() shard.Slot {
s := shard.Slot{
EcdsaAddress: makeTestAddress(maker.i),
BLSPublicKey: maker.kps[maker.i].Pub(), // Yes, will panic when not enough kps
}
maker.i++
return s
}
//
// State DB for testing
//
func makeTestStateDB() *state.DB {
db := state.NewDatabase(rawdb.NewMemoryDatabase())
sdb, err := state.New(common.Hash{}, db, nil)
if err != nil {
panic(err)
}
err = sdb.UpdateValidatorWrapper(offAddr, makeDefaultValidatorWrapper())
if err != nil {
panic(err)
}
return sdb
}
//
// BLS keys for testing
//
type blsKeyPair struct {
pri *bls_core.SecretKey
pub *bls_core.PublicKey
}
func genKeyPairs(size int) []blsKeyPair {
kps := make([]blsKeyPair, 0, size)
for i := 0; i != size; i++ {
kps = append(kps, genKeyPair())
}
return kps
}
func genKeyPair() blsKeyPair {
pri := bls.RandPrivateKey()
pub := pri.GetPublicKey()
return blsKeyPair{
pri: pri,
pub: pub,
}
}
func (kp blsKeyPair) Pub() bls.SerializedPublicKey {
var pub bls.SerializedPublicKey
copy(pub[:], kp.pub.Serialize())
return pub
}
func (kp blsKeyPair) Sign(block *types.Block) []byte {
chain := &fakeBlockChain{config: *params.LocalnetChainConfig}
msg := consensus_sig.ConstructCommitPayload(chain.Config(), block.Epoch(), block.Hash(),
block.Number().Uint64(), block.Header().ViewID().Uint64())
sig := kp.pri.SignHash(msg)
return sig.Serialize()
}
//
// Mock blockchain for testing
//
type fakeBlockChain struct {
config params.ChainConfig
currentBlock types.Block
superCommittee shard.State
snapshots map[common.Address]staking.ValidatorWrapper
}
func makeFakeBlockChain() *fakeBlockChain {
return &fakeBlockChain{
config: *params.LocalnetChainConfig,
currentBlock: *makeBlockForTest(currentEpoch, 0),
superCommittee: makeDefaultCommittee(),
snapshots: make(map[common.Address]staking.ValidatorWrapper),
Description: defaultDesc,
CreationHeight: big.NewInt(100),
}
}
func makeBlockForTest(epoch int64, index int) *types.Block {
h := blockfactory.NewTestHeader()
h.SetEpoch(big.NewInt(epoch))
h.SetNumber(big.NewInt(doubleSignBlockNumber))
h.SetViewID(big.NewInt(doubleSignViewID))
h.SetRoot(common.BigToHash(big.NewInt(int64(index))))
return types.NewBlockWithHeader(h)
}
func (bc *fakeBlockChain) CurrentBlock() *types.Block {
return &bc.currentBlock
}
func (bc *fakeBlockChain) CurrentHeader() *block.Header {
return bc.currentBlock.Header()
}
func (bc *fakeBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (bc *fakeBlockChain) GetHeader(hash common.Hash, number uint64) *block.Header { return nil }
func (bc *fakeBlockChain) GetHeaderByHash(hash common.Hash) *block.Header { return nil }
func (bc *fakeBlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { return nil }
func (bc *fakeBlockChain) ContractCode(hash common.Hash) ([]byte, error) { return []byte{}, nil }
func (bc *fakeBlockChain) ValidatorCode(hash common.Hash) ([]byte, error) { return []byte{}, nil }
func (bc *fakeBlockChain) ShardID() uint32 { return 0 }
func (bc *fakeBlockChain) ReadShardState(epoch *big.Int) (*shard.State, error) { return nil, nil }
func (bc *fakeBlockChain) TrieNode(hash common.Hash) ([]byte, error) { return []byte{}, nil }
func (bc *fakeBlockChain) WriteCommitSig(blockNum uint64, lastCommits []byte) error { return nil }
func (bc *fakeBlockChain) GetHeaderByNumber(number uint64) *block.Header { return nil }
func (bc *fakeBlockChain) ReadValidatorList() ([]common.Address, error) { return nil, nil }
func (bc *fakeBlockChain) ReadCommitSig(blockNum uint64) ([]byte, error) { return nil, nil }
func (bc *fakeBlockChain) ReadBlockRewardAccumulator(uint64) (*big.Int, error) { return nil, nil }
func (bc *fakeBlockChain) ValidatorCandidates() []common.Address { return nil }
func (cr *fakeBlockChain) ReadValidatorInformationAtState(addr common.Address, state *state.DB) (*staking.ValidatorWrapper, error) {
return nil, nil
}
func (bc *fakeBlockChain) ReadValidatorSnapshotAtEpoch(epoch *big.Int, offender common.Address) (*types2.ValidatorSnapshot, error) {
return &types2.ValidatorSnapshot{
Validator: makeDefaultValidatorWrapper(),
Epoch: epoch,
}, nil
}
func (bc *fakeBlockChain) ReadValidatorInformation(addr common.Address) (*staking.ValidatorWrapper, error) {
return nil, nil
}
func (bc *fakeBlockChain) Config() *params.ChainConfig {
return params.LocalnetChainConfig
}
func (cr *fakeBlockChain) StateAt(root common.Hash) (*state.DB, error) {
return nil, nil
}
func (bc *fakeBlockChain) ReadValidatorSnapshot(addr common.Address) (*staking.ValidatorSnapshot, error) {
return nil, nil
}
func (bc *fakeBlockChain) ReadValidatorStats(addr common.Address) (*staking.ValidatorStats, error) {
return nil, nil
}
func (bc *fakeBlockChain) SuperCommitteeForNextEpoch(beacon engine.ChainReader, header *block.Header, isVerify bool) (*shard.State, error) {
return nil, nil
}
//
// Fake header for testing
//
func makeFakeHeader() *block.Header {
h := blockfactory.NewTestHeader()
h.SetCoinbase(leaderAddr)
return h
}
//
// Utilities for testing
//
func makeTestAddress(item interface{}) common.Address {
s := fmt.Sprintf("harmony.one.%v", item)
return common.BytesToAddress([]byte(s))
}
func makeVoteData(kp blsKeyPair, block *types.Block) slash.Vote {
return slash.Vote{
SignerPubKeys: []bls.SerializedPublicKey{kp.Pub()},
BlockHeaderHash: block.Hash(),
Signature: kp.Sign(block),
// 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 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))
}
}
}

Loading…
Cancel
Save