Merge pull request #113 from prestonvanloon/fix/flag

Add sharding flags option to usage.

Former-commit-id: 172a2e8e071fc49557211c264695cc877a3cb537 [formerly dbe63e90a3b1eec1042f5bfe37dec22e6531f562]
Former-commit-id: 71aaf4a610d475f3e1aab9218fcc6339b41c96f9
This commit is contained in:
Preston Van Loon 2018-05-13 12:39:21 -04:00 committed by GitHub
commit 5c7ce3af5e
5 changed files with 892 additions and 205 deletions

View File

@ -83,6 +83,12 @@ var AppHelpFlagGroups = []flagGroup{
utils.LightKDFFlag,
},
},
{
Name: "SHARDING",
Flags: []cli.Flag{
utils.DepositFlag,
},
},
{Name: "DEVELOPER CHAIN",
Flags: []cli.Flag{
utils.DeveloperFlag,

View File

@ -55,6 +55,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sharding"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"gopkg.in/urfave/cli.v1"
)
@ -536,7 +537,7 @@ var (
//Sharding Settings
DepositFlag = cli.BoolFlag{
Name: "deposit",
Usage: "To become a notary with your sharding client, 100 ETH will be deposited from user's account into SMC ",
Usage: "To become a notary with your sharding client, " + new(big.Int).Div(sharding.NotaryDeposit, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)).String() + " ETH will be deposited into SMC",
}
)

File diff suppressed because one or more lines are too long

View File

@ -2,10 +2,11 @@ pragma solidity ^0.4.23;
contract SMC {
event HeaderAdded(uint indexed shardId, bytes32 chunkRoot, int128 period, address proposerAddress);
event HeaderAdded(uint indexed shardId, bytes32 chunkRoot, uint period, address proposerAddress);
event NotaryRegistered(address notary, uint poolIndex);
event NotaryDeregistered(address notary, uint poolIndex, uint deregisteredPeriod);
event NotaryReleased(address notary, uint poolIndex);
event VoteSubmitted(uint indexed shardId, bytes32 chunkRoot, uint period, address notaryAddress);
struct Notary {
uint deregisteredPeriod;
@ -13,39 +14,36 @@ contract SMC {
bool deposited;
}
struct CollationHeader {
uint shardId; // Number of the shard ID
struct CollationRecord {
bytes32 chunkRoot; // Root hash of the collation body
uint period; // Period which header should be included
address proposerAddress;
}
// Packed variables to be used in addHeader
struct HeaderVars {
bytes32 entireHeaderHash;
int score;
address notaryAddr;
bool isNewHead;
address proposer; // Address of the proposer
bool isElected; // True if the collation has reached quorum size
}
// Notary state variables
address[] public notaryPool;
// notaryAddress => notaryStruct
mapping (address => Notary) public notaryRegistry;
// shardId => (headerHash => treeRoot)
mapping (uint => mapping (bytes32 => bytes32)) public collationTrees;
// shardId => (headerHash => collationHeader)
mapping (int => mapping (bytes32 => CollationHeader)) public collationHeaders;
// shardId => headerHash
mapping (int => bytes32) shardHead;
// Number of notaries
// number of notaries
uint public notaryPoolLength;
// current vote count of each shard
// first 31 bytes are bitfield of notary's vote
// last 1 byte is the number of the total votes
mapping (uint => bytes32) public currentVote;
// Collation state variables
// shardId => (period => CollationHeader), collation records been appended by proposer
mapping (uint => mapping (uint => CollationRecord)) public collationRecords;
// shardId => period, last period of the submitted collation header
mapping (uint => uint) public lastSubmittedCollation;
// shardId => period, last period of the approved collation header
mapping (uint => uint) public lastApprovedCollation;
// Internal help functions variables
// Stack of empty notary slot indicies
uint[] emptySlotsStack;
// Top index of the stack
uint emptySlotsStackTop;
// Notary sample size at current period and next period
uint currentPeriodNotarySampleSize;
uint nextPeriodNotarySampleSize;
@ -69,12 +67,9 @@ contract SMC {
// Threshold(number of notaries in committee) for a proposal to be accepted
uint constant QUORUM_SIZE = 90;
// Log the latest period number of the shard
mapping (int => int) public periodHead;
/// Checks if a notary with given shard id and period has been chosen as
/// a committee member to vote for header added on to the main chain
function getNotaryInCommittee(uint shardId, uint _index) public view returns(address) {
function getNotaryInCommittee(uint _shardId, uint _index) public view returns(address) {
uint period = block.number / PERIOD_LENGTH;
// Determine notary pool length based on notary sample size
@ -88,7 +83,7 @@ contract SMC {
// Get the most recent block number before the period started
uint latestBlock = period * PERIOD_LENGTH - 1;
uint latestBlockHash = uint(block.blockhash(latestBlock));
uint index = uint(keccak256(latestBlockHash, _index, shardId)) % sampleSize;
uint index = uint(keccak256(latestBlockHash, _index, _shardId)) % sampleSize;
return notaryPool[index];
}
@ -100,7 +95,6 @@ contract SMC {
require(!notaryRegistry[notaryAddress].deposited);
require(msg.value == NOTARY_DEPOSIT);
// Track the numbers of participating notaries in between periods
updateNotarySampleSize();
uint index;
@ -136,7 +130,6 @@ contract SMC {
require(notaryRegistry[notaryAddress].deposited);
require(notaryPool[index] == notaryAddress);
// Track the numbers of participating notaries in between periods
updateNotarySampleSize();
uint deregisteredPeriod = block.number / PERIOD_LENGTH;
@ -162,31 +155,88 @@ contract SMC {
emit NotaryReleased(notaryAddress, index);
}
/// Calcuates the hash of the header from the input parameters
function computeHeaderHash(
uint256 shardId,
bytes32 parentHash,
bytes32 chunkRoot,
uint256 period,
address proposerAddress
) public returns(bytes32) {
/*
TODO: Calculate the hash of the collation header from the input parameters
*/
}
/// Add collation header to the main chain, anyone can call this function. It emits a log
function addHeader(
uint _shardId,
uint period,
bytes32 chunkRoot,
address proposerAddress
uint _period,
bytes32 _chunkRoot
) public {
/*
TODO: Anyone can call this at any time. The first header
to get included for a given shard in a given period gets in,
all others dont. This function just emits a log
*/
require((_shardId >= 0) && (_shardId < SHARD_COUNT));
require(_period == block.number / PERIOD_LENGTH);
require(_period > lastSubmittedCollation[_shardId]);
updateNotarySampleSize();
collationRecords[_shardId][_period] = CollationRecord({
chunkRoot: _chunkRoot,
proposer: msg.sender,
isElected: false
});
lastSubmittedCollation[_shardId] = block.number / PERIOD_LENGTH;
delete currentVote[_shardId];
emit HeaderAdded(_shardId, _chunkRoot, _period, msg.sender);
}
/// Sampled notary can call the following funtion to submit vote,
/// a vote log will be emitted for client to monitor
function submitVote(
uint _shardId,
uint _period,
uint _index,
bytes32 _chunkRoot
) public {
require((_shardId >= 0) && (_shardId < SHARD_COUNT));
require(_period == block.number / PERIOD_LENGTH);
require(_period == lastSubmittedCollation[_shardId]);
require(_index < COMMITTEE_SIZE);
require(_chunkRoot == collationRecords[_shardId][_period].chunkRoot);
require(notaryRegistry[msg.sender].deposited);
require(!hasVoted(_shardId, _index));
require(getNotaryInCommittee(_shardId, _index) == msg.sender);
castVote(_shardId, _index);
uint voteCount = getVoteCount(_shardId);
if (voteCount >= QUORUM_SIZE) {
lastApprovedCollation[_shardId] = _period;
collationRecords[_shardId][_period].isElected = true;
}
emit VoteSubmitted(_shardId, _chunkRoot, _period, msg.sender);
}
/// Returns total vote count of currentVote
/// the vote count is stored in the last byte of currentVote
function getVoteCount(uint _shardId) public view returns (uint) {
uint votes = uint(currentVote[_shardId]);
// Extra the last byte of currentVote
return votes % 256;
}
/// Check if a bit is set, this function is used to check
/// if a notary has casted the vote. Right shift currentVote by index
/// and AND with 1, return true if voted, false if not
function hasVoted(uint _shardId, uint _index) public view returns (bool) {
uint votes = uint(currentVote[_shardId]);
// Shift currentVote to right by given index
votes = votes >> (255 - _index);
// AND 1 to neglect everything but bit 0, then compare to 1
return votes & 1 == 1;
}
/// Check if the empty slots stack is empty
function emptyStack() internal view returns(bool) {
return emptySlotsStackTop == 0;
}
/// Save one uint into the empty slots stack for notary to use later
function stackPush(uint _index) internal {
if (emptySlotsStack.length == emptySlotsStackTop)
emptySlotsStack.push(_index);
else
emptySlotsStack[emptySlotsStackTop] = _index;
++emptySlotsStackTop;
}
/// To keep track of notary size in between periods, we call updateNotarySampleSize
@ -200,25 +250,24 @@ contract SMC {
sampleSizeLastUpdatedPeriod = currentPeriod;
}
/// Check if the empty slots stack is empty
function emptyStack() internal view returns(bool) {
return emptySlotsStackTop == 0;
}
/// Save one uint into the empty slots stack for notary to use later
function stackPush(uint index) internal {
if (emptySlotsStack.length == emptySlotsStackTop)
emptySlotsStack.push(index);
else
emptySlotsStack[emptySlotsStackTop] = index;
++emptySlotsStackTop;
}
/// Get one uint out of the empty slots stack for notary index
function stackPop() internal returns(uint) {
require(emptySlotsStackTop > 1);
--emptySlotsStackTop;
return emptySlotsStack[emptySlotsStackTop];
}
}
/// Set the index bit to one, notary uses this function to cast its vote,
/// after the notary casts its vote, we increase currentVote's count by 1
function castVote(uint _shardId, uint _index) internal {
uint votes = uint(currentVote[_shardId]);
// Get the bitfield by shifting 1 to the index
uint indexToFlag = 2 ** (255 - _index);
// OR with currentVote to cast notary index to 1
votes = votes | indexToFlag;
// Update vote count
votes++;
currentVote[_shardId] = bytes32(votes);
}
}

View File

@ -34,13 +34,14 @@ var (
ctx = context.Background()
)
// deploySMCContract is a helper function for deploying SMC.
func deploySMCContract(backend *backends.SimulatedBackend, key *ecdsa.PrivateKey) (common.Address, *types.Transaction, *SMC, error) {
transactOpts := bind.NewKeyedTransactor(key)
defer backend.Commit()
return DeploySMC(transactOpts, backend)
}
// Test creating the SMC contract.
// TestContractCreation tests SMC smart contract can successfully be deployed.
func TestContractCreation(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -51,6 +52,7 @@ func TestContractCreation(t *testing.T) {
}
}
// TestNotaryRegister tests notary registers in a normal condition.
func TestNotaryRegister(t *testing.T) {
const notaryCount = 3
var notaryPoolAddr [notaryCount]common.Address
@ -58,7 +60,7 @@ func TestNotaryRegister(t *testing.T) {
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initializes back end with 3 accounts and each with 2000 eth balances.
// Initializes back end with 3 accounts and each with 2000 eth balances.
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
@ -137,6 +139,7 @@ func TestNotaryRegister(t *testing.T) {
}
}
// TestNotaryRegisterInsufficientEther tests notary registers with insufficient deposit.
func TestNotaryRegisterInsufficientEther(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -162,6 +165,7 @@ func TestNotaryRegisterInsufficientEther(t *testing.T) {
}
// TestNotaryDoubleRegisters tests notary registers twice.
func TestNotaryDoubleRegisters(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -195,6 +199,7 @@ func TestNotaryDoubleRegisters(t *testing.T) {
}
// TestNotaryDeregister tests notary deregisters in a normal condition.
func TestNotaryDeregister(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -243,6 +248,7 @@ func TestNotaryDeregister(t *testing.T) {
}
}
// TestNotaryDeregisterThenRegister tests notary deregisters then registers before lock up ends.
func TestNotaryDeregisterThenRegister(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -288,6 +294,7 @@ func TestNotaryDeregisterThenRegister(t *testing.T) {
}
}
// TestNotaryRelease tests notary releases in a normal condition.
func TestNotaryRelease(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -359,6 +366,7 @@ func TestNotaryRelease(t *testing.T) {
}
}
// TestNotaryInstantRelease tests notary releases before lockup ends.
func TestNotaryInstantRelease(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
@ -419,8 +427,9 @@ func TestNotaryInstantRelease(t *testing.T) {
}
}
// TestCommitteeListsAreDifferent tests different shards have different notary committee.
func TestCommitteeListsAreDifferent(t *testing.T) {
const notaryCount = 10000
const notaryCount = 1000
var notaryPoolAddr [notaryCount]common.Address
var notaryPoolPrivKeys [notaryCount]*ecdsa.PrivateKey
var txOpts [notaryCount]*bind.TransactOpts
@ -441,33 +450,29 @@ func TestCommitteeListsAreDifferent(t *testing.T) {
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10000 notaries to SMC.
// Register 1000 notaries to SMC.
for i := 0; i < notaryCount; i++ {
smc.RegisterNotary(txOpts[i])
backend.Commit()
}
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(10000)) != 0 {
if numNotaries.Cmp(big.NewInt(notaryCount)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1000, Got: %v", numNotaries)
}
// get a list of sampled notaries from shard 0.
var shard0CommitteeList []string
for i := 0; i < int(sharding.NotaryCommitSize); i++ {
addr, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(0), big.NewInt(int64(i)))
shard0CommitteeList = append(shard0CommitteeList, addr.String())
}
// get a list of sampled notaries from shard 1, verify it's not identical to shard 0.
for i := 0; i < int(sharding.NotaryCommitSize); i++ {
addr, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(1), big.NewInt(int64(i)))
if shard0CommitteeList[i] == addr.String() {
// Compare sampled first 5 notaries of shard 0 to shard 1, they should not be identical.
for i := 0; i < 5; i++ {
addr0, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(0), big.NewInt(int64(i)))
addr1, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(1), big.NewInt(int64(i)))
if addr0 == addr1 {
t.Errorf("Shard 0 committee list is identical to shard 1's committee list")
}
}
}
// TestGetCommitteeWithNonMember tests unregistered notary tries to be in the committee.
func TestGetCommitteeWithNonMember(t *testing.T) {
const notaryCount = 11
var notaryPoolAddr [notaryCount]common.Address
@ -475,7 +480,7 @@ func TestGetCommitteeWithNonMember(t *testing.T) {
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initialize back end with 11 accounts and each with 2000 eth balances.
// Initialize back end with 11 accounts and each with 2000 eth balances.
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
@ -490,7 +495,7 @@ func TestGetCommitteeWithNonMember(t *testing.T) {
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10 notaries to SMC, leave 1 address free.
// Register 10 notaries to SMC, leave 1 address free.
for i := 0; i < 10; i++ {
smc.RegisterNotary(txOpts[i])
backend.Commit()
@ -501,7 +506,7 @@ func TestGetCommitteeWithNonMember(t *testing.T) {
t.Fatalf("Incorrect count from notary pool. Want: 135, Got: %v", numNotaries)
}
// verify the unregistered account is not in the notary pool list.
// Verify the unregistered account is not in the notary pool list.
for i := 0; i < 10; i++ {
addr, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(0), big.NewInt(int64(i)))
if notaryPoolAddr[10].String() == addr.String() {
@ -511,6 +516,7 @@ func TestGetCommitteeWithNonMember(t *testing.T) {
}
// TestGetCommitteeAfterDeregisters tests notary tries to be in committee after deregistered.
func TestGetCommitteeAfterDeregisters(t *testing.T) {
const notaryCount = 10
var notaryPoolAddr [notaryCount]common.Address
@ -518,7 +524,7 @@ func TestGetCommitteeAfterDeregisters(t *testing.T) {
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initialize back end with 10 accounts and each with 2000 eth balances.
// Initialize back end with 10 accounts and each with 2000 eth balances.
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
@ -533,7 +539,7 @@ func TestGetCommitteeAfterDeregisters(t *testing.T) {
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10 notaries to SMC.
// Register 10 notaries to SMC.
for i := 0; i < 10; i++ {
smc.RegisterNotary(txOpts[i])
backend.Commit()
@ -544,7 +550,7 @@ func TestGetCommitteeAfterDeregisters(t *testing.T) {
t.Errorf("Incorrect count from notary pool. Want: 10, Got: %v", numNotaries)
}
// deregister notary 0 from SMC.
// Deregister notary 0 from SMC.
txOpts[0].Value = big.NewInt(0)
smc.DeregisterNotary(txOpts[0])
backend.Commit()
@ -554,7 +560,7 @@ func TestGetCommitteeAfterDeregisters(t *testing.T) {
t.Errorf("Incorrect count from notary pool. Want: 9, Got: %v", numNotaries)
}
// verify degistered notary 0 is not in the notary pool list.
// Verify degistered notary 0 is not in the notary pool list.
for i := 0; i < 10; i++ {
addr, _ := smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(0), big.NewInt(int64(i)))
if notaryPoolAddr[0].String() == addr.String() {
@ -562,3 +568,419 @@ func TestGetCommitteeAfterDeregisters(t *testing.T) {
}
}
}
// TestNormalAddHeader tests proposer add header in normal condition.
func TestNormalAddHeader(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
p, err := smc.LastSubmittedCollation(&bind.CallOpts{}, shard0)
if err != nil {
t.Fatalf("Can't get last submitted collation's period number: %v", err)
}
if p.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect last period, when header was added. Got: %v", p)
}
cr, err := smc.CollationRecords(&bind.CallOpts{}, shard0, period1)
if cr.ChunkRoot != chunkRoot {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
// Fast forward to the next period. Period 2.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 2 and chunkroot 0xB.
period2 := big.NewInt(2)
chunkRoot = [32]byte{'B'}
_, err = smc.AddHeader(txOpts, shard0, period2, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
p, err = smc.LastSubmittedCollation(&bind.CallOpts{}, shard0)
if p.Cmp(big.NewInt(2)) != 0 {
t.Errorf("Incorrect last period, when header was added. Got: %v", p)
}
cr, err = smc.CollationRecords(&bind.CallOpts{}, shard0, period2)
if cr.ChunkRoot != chunkRoot {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
// Proposer adds header consists shard 1, period 2 and chunkroot 0xC.
shard1 := big.NewInt(1)
chunkRoot = [32]byte{'C'}
_, err = smc.AddHeader(txOpts, shard1, period2, chunkRoot)
backend.Commit()
p, err = smc.LastSubmittedCollation(&bind.CallOpts{}, shard1)
if p.Cmp(big.NewInt(2)) != 0 {
t.Errorf("Incorrect last period, when header was added. Got: %v", p)
}
cr, err = smc.CollationRecords(&bind.CallOpts{}, shard1, period2)
if cr.ChunkRoot != chunkRoot {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", cr, chunkRoot)
}
}
// TestAddTwoHeadersAtSamePeriod tests we can't add two headers within the same period.
func TestAddTwoHeadersAtSamePeriod(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
p, err := smc.LastSubmittedCollation(&bind.CallOpts{}, shard0)
if p.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect last period, when header was added. Got: %v", p)
}
cr, err := smc.CollationRecords(&bind.CallOpts{}, shard0, period1)
if cr.ChunkRoot != chunkRoot {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
// Proposer attempts to add another header chunkroot 0xB on the same period for the same shard.
chunkRoot = [32]byte{'B'}
_, err = smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err == nil {
t.Errorf("Proposer is not allowed to add 2 headers within same period")
}
}
// TestAddHeadersAtWrongPeriod tests proposer adds header in the wrong period.
func TestAddHeadersAtWrongPeriod(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header at wrong period, shard 0, period 0 and chunkroot 0xA.
period0 := big.NewInt(0)
shard0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
chunkRootEmpty := [32]byte{}
_, err := smc.AddHeader(txOpts, shard0, period0, chunkRoot)
if err == nil {
t.Errorf("Proposer adds header at wrong period should have failed")
}
cr, err := smc.CollationRecords(&bind.CallOpts{}, shard0, period0)
if cr.ChunkRoot != chunkRootEmpty {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
// Proposer adds header at wrong period, shard 0, period 2 and chunkroot 0xA.
period2 := big.NewInt(2)
_, err = smc.AddHeader(txOpts, shard0, period2, chunkRoot)
if err == nil {
t.Errorf("Proposer adds header at wrong period should have failed")
}
cr, err = smc.CollationRecords(&bind.CallOpts{}, shard0, period2)
if cr.ChunkRoot != chunkRootEmpty {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
// Proposer adds header at correct period, shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
_, err = smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
cr, err = smc.CollationRecords(&bind.CallOpts{}, shard0, period1)
if cr.ChunkRoot != chunkRootEmpty {
t.Errorf("Chunkroot mismatched. Want: %v, Got: %v", chunkRoot, cr)
}
}
// TestSubmitVote tests notary submit votes in normal condition.
func TestSubmitVote(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDeposit
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Notary 0 registers.
smc.RegisterNotary(txOpts)
backend.Commit()
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
index0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
txOpts.Value = big.NewInt(0)
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
// Notary 0 votes on header.
c, err := smc.GetVoteCount(&bind.CallOpts{}, shard0)
if err != nil {
t.Fatalf("Get notary vote count failed: %v", err)
}
if c.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary vote count, want: 0, got: %v", c)
}
_, err = smc.SubmitVote(txOpts, shard0, period1, index0, chunkRoot)
backend.Commit()
if err != nil {
t.Fatalf("Notary submits vote failed: %v", err)
}
// Check notary 0's vote is correctly casted.
v, err := smc.HasVoted(&bind.CallOpts{}, shard0, index0)
if err != nil {
t.Fatalf("Check notary's vote failed: %v", err)
}
if !v {
t.Errorf("Notary's indexd bit did not cast to 1 in index %v", index0)
}
c, err = smc.GetVoteCount(&bind.CallOpts{}, shard0)
if c.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect notary vote count, want: 1, got: %v", c)
}
// Check header's submitted with the current period, should be period 1.
p, err := smc.LastSubmittedCollation(&bind.CallOpts{}, shard0)
if err != nil {
t.Fatalf("Get period of last submitted header failed: %v", err)
}
if p.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect period submitted, want: 1, got: %v", p)
}
// Check header's approved with the current period, should be period 0.
p, err = smc.LastApprovedCollation(&bind.CallOpts{}, shard0)
if err != nil {
t.Fatalf("Get period of last approved header failed: %v", err)
}
if p.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect period submitted, want: 0, got: %v", p)
}
}
// TestSubmitVoteTwice tests notary tries to submit same vote twice.
func TestSubmitVoteTwice(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDeposit
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Notary 0 registers.
smc.RegisterNotary(txOpts)
backend.Commit()
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
index0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
txOpts.Value = big.NewInt(0)
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
// Notary 0 votes on header.
smc.SubmitVote(txOpts, shard0, period1, index0, chunkRoot)
backend.Commit()
// Check notary 0's vote is correctly casted.
c, _ := smc.GetVoteCount(&bind.CallOpts{}, shard0)
if c.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect notary vote count, want: 1, got: %v", c)
}
// Notary 0 votes on header again, it should fail.
_, err = smc.SubmitVote(txOpts, shard0, period1, index0, chunkRoot)
if err == nil {
t.Errorf("notary voting twice should have failed")
}
backend.Commit()
// Check notary 0's vote is correctly casted.
c, _ = smc.GetVoteCount(&bind.CallOpts{}, shard0)
if c.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect notary vote count, want: 1, got: %v", c)
}
}
// TestSubmitVoteByNonEligibleNotary tests a non-eligible notary tries to submit vote.
func TestSubmitVoteByNonEligibleNotary(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDeposit
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
index0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
txOpts.Value = big.NewInt(0)
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
// Unregistered Notary 0 votes on header, it should fail.
_, err = smc.SubmitVote(txOpts, shard0, period1, index0, chunkRoot)
backend.Commit()
if err == nil {
t.Errorf("Non registered notary submits vote should have failed")
}
c, _ := smc.GetVoteCount(&bind.CallOpts{}, shard0)
if c.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary vote count, want: 0, got: %v", c)
}
}
// TestSubmitVoteWithOutAHeader tests a notary tries to submit vote before header gets added.
func TestSubmitVoteWithOutAHeader(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDeposit
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Notary 0 registers.
smc.RegisterNotary(txOpts)
backend.Commit()
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
index0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
txOpts.Value = big.NewInt(0)
// Notary 0 votes on header, it should fail because no header has added.
_, err := smc.SubmitVote(txOpts, shard0, period1, index0, chunkRoot)
if err == nil {
t.Errorf("Notary votes should have failed due to missing header")
}
backend.Commit()
// Check notary 0's vote is correctly casted.
c, _ := smc.GetVoteCount(&bind.CallOpts{}, shard0)
if c.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary vote count, want: 1, got: %v", c)
}
}
// TestSubmitVoteWithInvalidArgs tests notary submits vote using wrong chunkroot and period.
func TestSubmitVoteWithInvalidArgs(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDeposit
_, _, smc, _ := deploySMCContract(backend, mainKey)
// Notary 0 registers
smc.RegisterNotary(txOpts)
backend.Commit()
// Fast forward to the next period to submit header. Period 1.
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Proposer adds header consists shard 0, period 1 and chunkroot 0xA.
period1 := big.NewInt(1)
shard0 := big.NewInt(0)
index0 := big.NewInt(0)
chunkRoot := [32]byte{'A'}
txOpts.Value = big.NewInt(0)
_, err := smc.AddHeader(txOpts, shard0, period1, chunkRoot)
if err != nil {
t.Fatalf("Proposer adds header failed: %v", err)
}
backend.Commit()
// Notary voting with incorrect period.
period2 := big.NewInt(2)
_, err = smc.SubmitVote(txOpts, shard0, period2, index0, chunkRoot)
if err == nil {
t.Errorf("Notary votes should have failed due to incorrect period")
}
// Notary voting with incorrect chunk root.
chunkRootWrong := [32]byte{'B'}
_, err = smc.SubmitVote(txOpts, shard0, period1, index0, chunkRootWrong)
if err == nil {
t.Errorf("Notary votes should have failed due to incorrect chunk root")
}
}