|
|
|
@ -3,305 +3,104 @@ package chain |
|
|
|
|
import ( |
|
|
|
|
"fmt" |
|
|
|
|
"math/big" |
|
|
|
|
"testing" |
|
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
|
"github.com/ethereum/go-ethereum/trie" |
|
|
|
|
bls_core "github.com/harmony-one/bls/ffi/go/bls" |
|
|
|
|
"github.com/harmony-one/harmony/block" |
|
|
|
|
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/core" |
|
|
|
|
"github.com/harmony-one/harmony/core/rawdb" |
|
|
|
|
"github.com/harmony-one/harmony/core/state" |
|
|
|
|
"github.com/harmony-one/harmony/core/state/snapshot" |
|
|
|
|
"github.com/harmony-one/harmony/core/types" |
|
|
|
|
"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/state/snapshot" |
|
|
|
|
"github.com/harmony-one/harmony/core/types" |
|
|
|
|
"github.com/harmony-one/harmony/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) |
|
|
|
|
staketest "github.com/harmony-one/harmony/staking/types/test" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
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), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|