package chain import ( "fmt" "math/big" "testing" "github.com/ethereum/go-ethereum/trie" bls_core "github.com/woop-chain/bls/ffi/go/bls" "github.com/woop-chain/woop/block" blockfactory "github.com/woop-chain/woop/block/factory" "github.com/woop-chain/woop/consensus/engine" consensus_sig "github.com/woop-chain/woop/consensus/signature" "github.com/woop-chain/woop/crypto/bls" "github.com/woop-chain/woop/numeric" "github.com/woop-chain/woop/shard" "github.com/woop-chain/woop/staking/effective" "github.com/woop-chain/woop/staking/slash" staking "github.com/woop-chain/woop/staking/types" types2 "github.com/woop-chain/woop/staking/types" "github.com/ethereum/go-ethereum/common" "github.com/woop-chain/woop/core/rawdb" "github.com/woop-chain/woop/core/state" "github.com/woop-chain/woop/core/state/snapshot" "github.com/woop-chain/woop/core/types" "github.com/woop-chain/woop/internal/params" ) 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 offenderShard = doubleSignShardID offenderShardIndex = 0 ) var ( doubleSignBlock1 = makeBlockForTest(doubleSignEpoch, 0) doubleSignBlock2 = makeBlockForTest(doubleSignEpoch, 1) ) 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) } 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) } beneficiaryBalanceAfterSlash := state.GetBalance(leaderAddr) if beneficiaryBalanceAfterSlash.Cmp(expRewardToBeneficiary) != 0 { t.Errorf("Slashing reward was not added properly to beneficiary: %v/%v", expRewardToBeneficiary, beneficiaryBalanceAfterSlash) } } // // 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"), } } // // Make validator for testing // func makeDefaultValidatorWrapper() *staking.ValidatorWrapper { pubKeys := []bls.SerializedPublicKey{offPub} v := defaultTestValidator(pubKeys) ds := staking.Delegations{} ds = append(ds, staking.Delegation{ DelegatorAddress: offAddr, Amount: new(big.Int).Set(fourtyKOnes), }) return &staking.ValidatorWrapper{ Validator: v, Delegations: ds, } } 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"), }, 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), } } 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) TrieDB() *trie.Database { return 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 (cr *fakeBlockChain) Snapshots() *snapshot.Tree { return 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("wikiwoop.com.%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), } }