sharding: beginning the shard interface

Former-commit-id: ba302afa3fac69fd4ccd3253a5e2a62728d1ffa5 [formerly 7bf1d53b8710efde5ab98460893632196547cd46]
Former-commit-id: 0cbdd232baf17fa348af742e18d8b4c533a9fc63
This commit is contained in:
Raul Jordan 2018-05-02 11:15:24 -05:00
commit 69408136ff
12 changed files with 1106 additions and 785 deletions

View File

@ -168,7 +168,7 @@ func init() {
attachCommand,
javascriptCommand,
// See shardingcmd.go:
collatorClientCommand,
notaryClientCommand,
proposerClientCommand,
// See misccmd.go:
makecacheCommand,

View File

@ -1,7 +1,7 @@
package main
import (
"github.com/ethereum/go-ethereum/sharding/collator"
"github.com/ethereum/go-ethereum/sharding/notary"
"github.com/ethereum/go-ethereum/sharding/proposer"
"github.com/ethereum/go-ethereum/cmd/utils"
@ -9,16 +9,16 @@ import (
)
var (
collatorClientCommand = cli.Command{
Action: utils.MigrateFlags(collatorClient),
Name: "sharding-collator",
Aliases: []string{"shard-collator"},
Usage: "Start a sharding collator client",
notaryClientCommand = cli.Command{
Action: utils.MigrateFlags(notaryClient),
Name: "sharding-notary",
Aliases: []string{"shard-notary"},
Usage: "Start a sharding notary client",
ArgsUsage: "[endpoint]",
Flags: []cli.Flag{utils.DataDirFlag, utils.PasswordFileFlag, utils.NetworkIdFlag, utils.IPCPathFlag, utils.DepositFlag},
Category: "SHARDING COMMANDS",
Description: `
Launches a sharding collator client that connects to a running geth node and submit collations to a Sharding Manager Contract. This feature is a work in progress.
Launches a sharding notary client that connects to a running geth node and submit collations to a Sharding Manager Contract. This feature is a work in progress.
`,
}
proposerClientCommand = cli.Command{
@ -30,13 +30,13 @@ Launches a sharding collator client that connects to a running geth node and sub
Flags: []cli.Flag{utils.DataDirFlag, utils.PasswordFileFlag, utils.NetworkIdFlag, utils.IPCPathFlag},
Category: "SHARDING COMMANDS",
Description: `
Launches a sharding proposer client that connects to a running geth node and proposes collations to collator node. This feature is a work in progress.
Launches a sharding proposer client that connects to a running geth node and proposes collations to notary node. This feature is a work in progress.
`,
}
)
func collatorClient(ctx *cli.Context) error {
c := collator.NewCollator(ctx)
func notaryClient(ctx *cli.Context) error {
c := notary.NewNotary(ctx)
return c.Start()
}

View File

@ -536,7 +536,7 @@ var (
//Sharding Settings
DepositFlag = cli.BoolFlag{
Name: "deposit",
Usage: "To become a collator with your sharding client, 100 ETH will be deposited from user's account into SMC ",
Usage: "To become a notary with your sharding client, 100 ETH will be deposited from user's account into SMC ",
}
)

View File

@ -33,7 +33,7 @@ const (
clientIdentifier = "geth" // Used to determine the ipc name.
)
// General client for Collator/Proposer. Communicates to Geth node via JSON RPC.
// General client for Notary/Proposer. Communicates to Geth node via JSON RPC.
type shardingClient struct {
endpoint string // Endpoint to JSON RPC
@ -96,7 +96,7 @@ func (c *shardingClient) Start() error {
c.rpcClient = rpcClient
c.client = ethclient.NewClient(rpcClient)
// Check account existence and unlock account before starting collator client
// Check account existence and unlock account before starting notary client
accounts := c.keystore.Accounts()
if len(accounts) == 0 {
return fmt.Errorf("no accounts found")

View File

@ -1,41 +0,0 @@
// Package collator holds all of the functionality to run as a collator in a sharded system.
package collator
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/sharding/client"
cli "gopkg.in/urfave/cli.v1"
)
// Collator runnable client.
type Collator interface {
// Start the main routine for a collator.
Start() error
}
type collator struct {
client client.Client
}
// NewCollator creates a new collator instance.
func NewCollator(ctx *cli.Context) Collator {
return &collator{
client: client.NewClient(ctx),
}
}
// Start the main routine for a collator.
func (c *collator) Start() error {
log.Info("Starting collator client")
err := c.client.Start()
if err != nil {
return err
}
defer c.client.Close()
if err := joinCollatorPool(c.client); err != nil {
return err
}
return subscribeBlockHeaders(c.client)
}

File diff suppressed because one or more lines are too long

View File

@ -1,253 +1,224 @@
pragma solidity ^0.4.19;
pragma solidity ^0.4.23;
contract SMC {
event TxToShard(address indexed to, int indexed shardId, int receiptId);
event CollationAdded(int indexed shardId, uint expectedPeriodNumber,
bytes32 periodStartPrevHash, bytes32 parentHash,
bytes32 transactionRoot, address coinbase,
bytes32 stateRoot, bytes32 receiptRoot,
int number, bool isNewHead, int score);
event Deposit(address collator, int index);
event Withdraw(int index);
event HeaderAdded(uint indexed shardId, bytes32 chunkRoot, int128 period, address proposerAddress);
event NotaryRegistered(address notary, uint poolIndex);
event NotaryDeregistered(address notary, uint poolIndex, uint deregisteredPeriod);
event NotaryReleased(address notary, uint poolIndex);
struct Collator {
// Amount of wei the collator holds
uint deposit;
// The collator's address
address addr;
}
struct Notary {
uint deregisteredPeriod;
uint poolIndex;
bool deposited;
}
struct CollationHeader {
bytes32 parentHash;
int score;
}
struct Receipt {
int shardId;
uint txStartgas;
uint txGasprice;
uint value;
bytes32 data;
address sender;
address to;
}
struct CollationHeader {
uint shardId; // Number of the shard ID
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 collatorAddr;
bool isNewHead;
}
// collatorId => Collators
mapping (int => Collator) public collators;
// shardId => (headerHash => CollationHeader)
mapping (int => mapping (bytes32 => CollationHeader)) public collationHeaders;
// receiptId => Receipt
mapping (int => Receipt) public receipts;
// shardId => headerHash
mapping (int => bytes32) shardHead;
// Number of collators
int public numCollators;
// Number of receipts
int numReceipts;
// Indexs of empty slots caused by the function `withdraw`
mapping (int => int) emptySlotsStack;
// The top index of the stack in empty_slots_stack
int emptySlotsStackTop;
// Has the collator deposited before?
mapping (address => bool) public isCollatorDeposited;
// Constant values
uint constant periodLength = 5;
int constant public shardCount = 100;
// The exact deposit size which you have to deposit to become a collator
uint constant depositSize = 1000 ether;
// Number of periods ahead of current period, which the contract
// is able to return the collator of that period
uint constant lookAheadPeriods = 4;
// Log the latest period number of the shard
mapping (int => int) public periodHead;
function SMC() public {
}
// Returns the gas limit that collations can currently have (by default make
// this function always answer 10 million).
function getCollationGasLimit() public pure returns(uint) {
return 10000000;
}
// Uses a block hash as a seed to pseudorandomly select a signer from the collator pool.
// [TODO] Chance of being selected should be proportional to the collator's deposit.
// Should be able to return a value for the current period or any future period up to.
function getEligibleCollator(int _shardId, uint _period) public view returns(address) {
require(_period >= lookAheadPeriods);
require((_period - lookAheadPeriods) * periodLength < block.number);
require(numCollators > 0);
// [TODO] Should check further if this safe or not
return collators[
int(
uint(
keccak256(
uint(block.blockhash((_period - lookAheadPeriods) * periodLength)),
_shardId
)
) %
uint(getCollatorsMaxIndex())
)
].addr;
}
function deposit() public payable returns(int) {
require(!isCollatorDeposited[msg.sender]);
require(msg.value == depositSize);
// Find the empty slot index in collators pool
int index;
if (!isStackEmpty())
index = stackPop();
else
index = int(numCollators);
collators[index] = Collator({
deposit: msg.value,
addr: msg.sender
});
++numCollators;
isCollatorDeposited[msg.sender] = true;
Deposit(msg.sender, index);
return index;
}
// Removes the collator from the collator pool and refunds the deposited ether
function withdraw(int _collatorIndex) public {
require(msg.sender == collators[_collatorIndex].addr);
// [FIXME] Should consider calling the collator's contract, might be useful
// when the collator is a contract.
collators[_collatorIndex].addr.transfer(collators[_collatorIndex].deposit);
isCollatorDeposited[collators[_collatorIndex].addr] = false;
delete collators[_collatorIndex];
stackPush(_collatorIndex);
--numCollators;
Withdraw(_collatorIndex);
}
// Attempts to process a collation header, returns true on success, reverts on failure.
function addHeader(int _shardId, uint _expectedPeriodNumber, bytes32 _periodStartPrevHash,
bytes32 _parentHash, bytes32 _transactionRoot,
address _coinbase, bytes32 _stateRoot, bytes32 _receiptRoot,
int _number) public returns(bool) {
HeaderVars memory headerVars;
// Check if the header is valid
require((_shardId >= 0) && (_shardId < shardCount));
require(block.number >= periodLength);
require(_expectedPeriodNumber == block.number / periodLength);
require(_periodStartPrevHash == block.blockhash(_expectedPeriodNumber * periodLength - 1));
// Check if this header already exists
headerVars.entireHeaderHash = keccak256(_shardId, _expectedPeriodNumber, _periodStartPrevHash,
_parentHash, _transactionRoot, bytes32(_coinbase),
_stateRoot, _receiptRoot, _number);
assert(collationHeaders[_shardId][headerVars.entireHeaderHash].score == 0);
// Check whether the parent exists.
// if (parent_collation_hash == 0), i.e., is the genesis,
// then there is no need to check.
if (_parentHash != 0x0)
assert(collationHeaders[_shardId][_parentHash].score > 0);
// Check if only one collation in one period
assert(periodHead[_shardId] < int(_expectedPeriodNumber));
// Check the signature with validation_code_addr
headerVars.collatorAddr = getEligibleCollator(_shardId, block.number/periodLength);
require(headerVars.collatorAddr != 0x0);
require(msg.sender == headerVars.collatorAddr);
// Check score == collationNumber
headerVars.score = collationHeaders[_shardId][_parentHash].score + 1;
require(_number == headerVars.score);
// Add the header
collationHeaders[_shardId][headerVars.entireHeaderHash] = CollationHeader({
parentHash: _parentHash,
score: headerVars.score
});
// Update the latest period number
periodHead[_shardId] = int(_expectedPeriodNumber);
// Determine the head
if (headerVars.score > collationHeaders[_shardId][shardHead[_shardId]].score) {
shardHead[_shardId] = headerVars.entireHeaderHash;
headerVars.isNewHead = true;
struct HeaderVars {
bytes32 entireHeaderHash;
int score;
address notaryAddr;
bool isNewHead;
}
CollationAdded(_shardId, _expectedPeriodNumber, _periodStartPrevHash,
_parentHash, _transactionRoot, _coinbase, _stateRoot,
_receiptRoot, _number, headerVars.isNewHead, headerVars.score);
address[] public notaryPool;
return true;
}
// 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;
// Records a request to deposit msg.value ETH to address to in shard shard_id
// during a future collation. Saves a `receipt ID` for this request,
// also saving `msg.sender`, `msg.value`, `to`, `shard_id`, `startgas`,
// `gasprice`, and `data`.
function txToShard(address _to, int _shardId, uint _txStartgas, uint _txGasprice,
bytes12 _data) public payable returns(int) {
receipts[numReceipts] = Receipt({
shardId: _shardId,
txStartgas: _txStartgas,
txGasprice: _txGasprice,
value: msg.value,
sender: msg.sender,
to: _to,
data: _data
});
var receiptId = numReceipts;
++numReceipts;
// Number of notaries
uint public notaryPoolLength;
// Stack of empty notary slot indicies
uint[] emptySlotsStack;
// Top index of the stack
uint emptySlotsStackTop;
TxToShard(_to, _shardId, receiptId);
return receiptId;
}
// Notary sample size at current period and next period
uint currentPeriodNotarySampleSize;
uint nextPeriodNotarySampleSize;
uint sampleSizeLastUpdatedPeriod;
function updateGasPrice(int _receiptId, uint _txGasprice) public payable returns(bool) {
require(receipts[_receiptId].sender == msg.sender);
receipts[_receiptId].txGasprice = _txGasprice;
return true;
}
// Constant values
uint constant PERIOD_LENGTH = 5;
// Number of shards
uint constant SHARD_COUNT = 100;
// The minimum deposit size for a notary
uint constant NOTARY_DEPOSIT = 1000 ether;
// The reward for notary on voting for a collation
uint constant NOTARY_REWARD = 0.001 ether;
// Time the ether is locked by notaries
uint constant NOTARY_LOCKUP_LENGTH = 16128;
// Number of periods ahead of current period, which the contract
// is able to return the notary of that period
uint constant LOOKAHEAD_LENGTH = 4;
// Number of notaries to select from notary pool for each shard in each period
uint constant COMMITTEE_SIZE = 135;
// Threshold(number of notaries in committee) for a proposal to be accepted
uint constant QUORUM_SIZE = 90;
function isStackEmpty() internal view returns(bool) {
return emptySlotsStackTop == 0;
}
// Log the latest period number of the shard
mapping (int => int) public periodHead;
function stackPush(int index) internal {
emptySlotsStack[emptySlotsStackTop] = index;
++emptySlotsStackTop;
}
/// 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) {
uint period = block.number / PERIOD_LENGTH;
function stackPop() internal returns(int) {
if (isStackEmpty())
return -1;
--emptySlotsStackTop;
return emptySlotsStack[emptySlotsStackTop];
}
// Determine notary pool length based on notary sample size
uint sampleSize;
if (period > sampleSizeLastUpdatedPeriod) {
sampleSize = nextPeriodNotarySampleSize;
} else {
sampleSize = currentPeriodNotarySampleSize;
}
function getCollatorsMaxIndex() internal view returns(int) {
int activateCollatorNum = 0;
int allCollatorSlotsNum = numCollators + emptySlotsStackTop;
// 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;
// TODO: any better way to iterate the mapping?
for (int i = 0; i < 1024; ++i) {
if (i >= allCollatorSlotsNum)
break;
if (collators[i].addr != 0x0)
activateCollatorNum += 1;
return notaryPool[index];
}
return activateCollatorNum + emptySlotsStackTop;
}
}
/// Registers notary to notatery registry, locks in the notary deposit,
/// and returns true on success
function registerNotary() public payable {
address notaryAddress = msg.sender;
require(!notaryRegistry[notaryAddress].deposited);
require(msg.value == NOTARY_DEPOSIT);
// Track the numbers of participating notaries in between periods
updateNotarySampleSize();
uint index;
if (emptyStack()) {
index = notaryPoolLength;
notaryPool.push(notaryAddress);
} else {
index = stackPop();
notaryPool[index] = notaryAddress;
}
++notaryPoolLength;
notaryRegistry[notaryAddress] = Notary({
deregisteredPeriod: 0,
poolIndex: index,
deposited: true
});
// if current index is greater than notary sample size, increase notary sample size for next period
if (index >= nextPeriodNotarySampleSize) {
nextPeriodNotarySampleSize = index + 1;
}
emit NotaryRegistered(notaryAddress, index);
}
/// Deregisters notary from notatery registry, lock up period countdowns down,
/// notary may call releaseNotary after lock up period finishses to withdraw deposit,
/// and returns true on success
function deregisterNotary() public {
address notaryAddress = msg.sender;
uint index = notaryRegistry[notaryAddress].poolIndex;
require(notaryRegistry[notaryAddress].deposited);
require(notaryPool[index] == notaryAddress);
// Track the numbers of participating notaries in between periods
updateNotarySampleSize();
uint deregisteredPeriod = block.number / PERIOD_LENGTH;
notaryRegistry[notaryAddress].deregisteredPeriod = deregisteredPeriod;
stackPush(index);
delete notaryPool[index];
--notaryPoolLength;
emit NotaryDeregistered(notaryAddress, index, deregisteredPeriod);
}
/// Removes an entry from notary registry, returns deposit back to the notary,
/// and returns true on success.
function releaseNotary() public {
address notaryAddress = msg.sender;
uint index = notaryRegistry[notaryAddress].poolIndex;
require(notaryRegistry[notaryAddress].deposited == true);
require(notaryRegistry[notaryAddress].deregisteredPeriod != 0);
require((block.number / PERIOD_LENGTH) > (notaryRegistry[notaryAddress].deregisteredPeriod + NOTARY_LOCKUP_LENGTH));
delete notaryRegistry[notaryAddress];
notaryAddress.transfer(NOTARY_DEPOSIT);
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
) 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
*/
}
/// To keep track of notary size in between periods, we call updateNotarySampleSize
/// before notary registration/deregistration so correct size can be applied next period
function updateNotarySampleSize() internal {
uint currentPeriod = block.number / PERIOD_LENGTH;
if (currentPeriod < sampleSizeLastUpdatedPeriod) {
return;
}
currentPeriodNotarySampleSize = nextPeriodNotarySampleSize;
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];
}
}

View File

@ -1,25 +1,40 @@
package contracts
import (
"crypto/ecdsa"
"math/big"
"testing"
"context"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/sharding"
)
type SMCConfig struct {
notaryLockupLenght *big.Int
proposerLockupLength *big.Int
}
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
accountBalance1001Eth, _ = new(big.Int).SetString("1001000000000000000000", 10)
collatorDeposit, _ = new(big.Int).SetString("1000000000000000000000", 10)
mainKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
accountBalance2000Eth, _ = new(big.Int).SetString("2000000000000000000000", 10)
notaryDepositInsufficient, _ = new(big.Int).SetString("999000000000000000000", 10)
notaryDeposit, _ = new(big.Int).SetString("1000000000000000000000", 10)
FastForward100Blocks = 100
smcConfig = SMCConfig{
notaryLockupLenght: new(big.Int).SetInt64(1),
proposerLockupLength: new(big.Int).SetInt64(1),
}
ctx = context.Background()
)
func deploySMCContract(backend *backends.SimulatedBackend) (common.Address, *types.Transaction, *SMC, error) {
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)
@ -27,104 +42,523 @@ func deploySMCContract(backend *backends.SimulatedBackend) (common.Address, *typ
// Test creating the SMC contract
func TestContractCreation(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
_, _, _, err := deploySMCContract(backend)
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
_, _, _, err := deploySMCContract(backend, mainKey)
backend.Commit()
if err != nil {
t.Fatalf("can't deploy SMC: %v", err)
}
}
// Test getting the collation gas limit
func TestGetCollationGasLimit(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
_, _, smc, _ := deploySMCContract(backend)
gasLimit, err := smc.GetCollationGasLimit(&bind.CallOpts{})
func TestNotaryRegister(t *testing.T) {
const notaryCount = 3
var notaryPoolAddr [notaryCount]common.Address
var notaryPoolPrivKeys [notaryCount]*ecdsa.PrivateKey
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initializes back end with 3 accounts and each with 2000 eth balances
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
notaryPoolAddr[i] = crypto.PubkeyToAddress(key.PublicKey)
txOpts[i] = bind.NewKeyedTransactor(key)
txOpts[i].Value = notaryDeposit
genesis[notaryPoolAddr[i]] = core.GenesisAccount{
Balance: accountBalance2000Eth,
}
}
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// Notary 0 has not registered
notary, err := smc.NotaryRegistry(&bind.CallOpts{}, notaryPoolAddr[0])
if err != nil {
t.Fatalf("Error getting collationGasLimit: %v", err)
t.Fatalf("Can't get notary registry info: %v", err)
}
if gasLimit.Cmp(big.NewInt(10000000)) != 0 {
t.Fatalf("collation gas limit should be 10000000 gas")
if notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
}
// Test collator deposit
func TestCollatorDeposit(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
transactOpts := bind.NewKeyedTransactor(key)
_, _, smc, _ := deploySMCContract(backend)
// Test deposit() function
// Deposit 100 Eth
transactOpts.Value = collatorDeposit
if _, err := smc.Deposit(transactOpts); err != nil {
t.Fatalf("Collator cannot deposit: %v", err)
// Test notary 0 has registered
if _, err := smc.RegisterNotary(txOpts[0]); err != nil {
t.Fatalf("Registering notary has failed: %v", err)
}
backend.Commit()
// Check updated number of collators
numCollators, err := smc.NumCollators(&bind.CallOpts{})
if err != nil {
t.Fatalf("Failed to get number of collators: %v", err)
}
if numCollators.Cmp(big.NewInt(1)) != 0 {
t.Fatalf("Failed to update number of collators")
notary, err = smc.NotaryRegistry(&bind.CallOpts{}, notaryPoolAddr[0])
if !notary.Deposited ||
notary.PoolIndex.Cmp(big.NewInt(0)) != 0 ||
notary.DeregisteredPeriod.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary registry. Want - deposited:true, index:0, period:0"+
"Got - deposited:%v, index:%v, period:%v ", notary.Deposited, notary.PoolIndex, notary.DeregisteredPeriod)
}
// Check collator structure
collatorStruct, err := smc.Collators(&bind.CallOpts{}, big.NewInt(0))
if err != nil {
t.Fatalf("Failed to get collator structure: %v", err)
}
if collatorStruct.Addr != addr {
t.Fatalf("Wrong collator address, %v should be %v", collatorStruct.Addr, addr)
}
if collatorStruct.Deposit.Cmp(collatorDeposit) != 0 {
t.Fatalf("Wrong collator deposit, %v should be %v", collatorStruct.Deposit, collatorDeposit)
}
// Check for the Deposit event
depositsEventsIterator, err := smc.FilterDeposit(&bind.FilterOpts{})
if err != nil {
t.Fatalf("Failed to get Deposit event: %v", err)
}
if !depositsEventsIterator.Next() {
t.Fatal("No Deposit event found")
}
if depositsEventsIterator.Event.Collator != addr {
t.Fatalf("Collator address mismatch: %x should be %x", depositsEventsIterator.Event.Collator, addr)
}
if depositsEventsIterator.Event.Index.Cmp(big.NewInt(0)) != 0 {
t.Fatalf("Collator index mismatch: %d should be 0", depositsEventsIterator.Event.Index)
}
}
// Test collator withdraw
func TestCollatorWithdraw(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
transactOpts := bind.NewKeyedTransactor(key)
_, _, smc, _ := deploySMCContract(backend)
transactOpts.Value = collatorDeposit
smc.Deposit(transactOpts)
transactOpts.Value = big.NewInt(0)
_, err := smc.Withdraw(transactOpts, big.NewInt(0))
if err != nil {
t.Fatalf("Failed to withdraw: %v", err)
// Test notary 1 and 2 have registered
if _, err := smc.RegisterNotary(txOpts[1]); err != nil {
t.Fatalf("Registering notary has failed: %v", err)
}
backend.Commit()
// Check for the Withdraw event
withdrawsEventsIterator, err := smc.FilterWithdraw(&bind.FilterOpts{Start: 0})
if _, err := smc.RegisterNotary(txOpts[2]); err != nil {
t.Fatalf("Registering notary has failed: %v", err)
}
backend.Commit()
notary, err = smc.NotaryRegistry(&bind.CallOpts{}, notaryPoolAddr[1])
if !notary.Deposited ||
notary.PoolIndex.Cmp(big.NewInt(1)) != 0 ||
notary.DeregisteredPeriod.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary registry. Want - deposited:true, index:1, period:0"+
"Got - deposited:%v, index:%v, period:%v ", notary.Deposited, notary.PoolIndex, notary.DeregisteredPeriod)
}
notary, err = smc.NotaryRegistry(&bind.CallOpts{}, notaryPoolAddr[2])
if !notary.Deposited ||
notary.PoolIndex.Cmp(big.NewInt(2)) != 0 ||
notary.DeregisteredPeriod.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect notary registry. Want - deposited:true, index:2, period:0"+
"Got - deposited:%v, index:%v, period:%v ", notary.Deposited, notary.PoolIndex, notary.DeregisteredPeriod)
}
// Check total numbers of notaries in pool
numNotaries, err := smc.NotaryPoolLength(&bind.CallOpts{})
if err != nil {
t.Fatalf("Failed to get withdraw event: %v", err)
t.Fatalf("Failed to get notary pool length: %v", err)
}
if !withdrawsEventsIterator.Next() {
t.Fatal("No withdraw event found")
}
if withdrawsEventsIterator.Event.Index.Cmp(big.NewInt(0)) != 0 {
t.Fatalf("Collator index mismatch: %d should be 0", withdrawsEventsIterator.Event.Index)
if numNotaries.Cmp(big.NewInt(3)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 3, Got: %v", numNotaries)
}
}
func TestNotaryRegisterInsufficientEther(t *testing.T) {
addr := crypto.PubkeyToAddress(mainKey.PublicKey)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance2000Eth}})
txOpts := bind.NewKeyedTransactor(mainKey)
txOpts.Value = notaryDepositInsufficient
_, _, smc, _ := deploySMCContract(backend, mainKey)
_, err := smc.RegisterNotary(txOpts)
if err == nil {
t.Errorf("Notary register should have failed with insufficient deposit")
}
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if notary.Deposited {
t.Errorf("Notary deposited with insufficient fund")
}
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
}
func TestNotaryDoubleRegisters(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()
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if !notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
// Notary 0 registers again
_, err := smc.RegisterNotary(txOpts)
if err == nil {
t.Errorf("Notary register should have failed with double registers")
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
}
func TestNotaryDeregister(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()
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if !notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
// Fast forward 100 blocks to check notary's deregistered period field is set correctly
for i := 0; i < FastForward100Blocks; i++ {
backend.Commit()
}
// Notary 0 deregisters
txOpts = bind.NewKeyedTransactor(mainKey)
_, err := smc.DeregisterNotary(txOpts)
if err != nil {
t.Fatalf("Failed to deregister notary: %v", err)
}
backend.Commit()
// Verify notary has saved the deregistered period as: current block number / period length
notary, _ = smc.NotaryRegistry(&bind.CallOpts{}, addr)
currentPeriod := big.NewInt(int64(FastForward100Blocks) / sharding.PeriodLength)
if currentPeriod.Cmp(notary.DeregisteredPeriod) != 0 {
t.Errorf("Incorrect notary degister period. Want: %v, Got: %v ", currentPeriod, notary.DeregisteredPeriod)
}
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
}
func TestNotaryDeregisterThenRegister(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()
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if !notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
// Notary 0 deregisters
txOpts = bind.NewKeyedTransactor(mainKey)
_, err := smc.DeregisterNotary(txOpts)
if err != nil {
t.Fatalf("Failed to deregister notary: %v", err)
}
backend.Commit()
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
// Notary 0 re-registers again
smc.RegisterNotary(txOpts)
backend.Commit()
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
}
func TestNotaryRelease(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()
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if !notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
// Fast forward to the next period to deregister
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Notary 0 deregisters
txOpts = bind.NewKeyedTransactor(mainKey)
_, err := smc.DeregisterNotary(txOpts)
if err != nil {
t.Fatalf("Failed to deregister notary: %v", err)
}
backend.Commit()
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
// Fast forward until lockup ends
for i := 0; i < int(sharding.NotaryLockupLength*sharding.PeriodLength+1); i++ {
backend.Commit()
}
// Notary 0 releases
_, err = smc.ReleaseNotary(txOpts)
if err != nil {
t.Fatalf("Failed to release notary: %v", err)
}
backend.Commit()
notary, err = smc.NotaryRegistry(&bind.CallOpts{}, addr)
if err != nil {
t.Fatalf("Can't get notary registry info: %v", err)
}
if notary.Deposited {
t.Errorf("Notary deposit flag should be false after released")
}
balance, err := backend.BalanceAt(ctx, addr, nil)
if err != nil {
t.Errorf("Can't get account balance, err: %s", err)
}
if balance.Cmp(notaryDeposit) < 0 {
t.Errorf("Notary did not receive deposit after lock up ends")
}
}
func TestNotaryInstantRelease(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()
notary, _ := smc.NotaryRegistry(&bind.CallOpts{}, addr)
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if !notary.Deposited {
t.Errorf("Notary has not registered. Got deposited flag: %v", notary.Deposited)
}
if numNotaries.Cmp(big.NewInt(1)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 1, Got: %v", numNotaries)
}
// Fast forward to the next period to deregister
for i := 0; i < int(sharding.PeriodLength); i++ {
backend.Commit()
}
// Notary 0 deregisters
txOpts = bind.NewKeyedTransactor(mainKey)
_, err := smc.DeregisterNotary(txOpts)
if err != nil {
t.Fatalf("Failed to deregister notary: %v", err)
}
backend.Commit()
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(0)) != 0 {
t.Fatalf("Incorrect count from notary pool. Want: 0, Got: %v", numNotaries)
}
// Notary 0 tries to release before lockup ends
_, err = smc.ReleaseNotary(txOpts)
backend.Commit()
notary, err = smc.NotaryRegistry(&bind.CallOpts{}, addr)
if err != nil {
t.Fatalf("Can't get notary registry info: %v", err)
}
if !notary.Deposited {
t.Errorf("Notary deposit flag should be true before released")
}
balance, err := backend.BalanceAt(ctx, addr, nil)
if balance.Cmp(notaryDeposit) > 0 {
t.Errorf("Notary received deposit before lockup ends")
}
}
func TestCommitteeListsAreDifferent(t *testing.T) {
const notaryCount = 10000
var notaryPoolAddr [notaryCount]common.Address
var notaryPoolPrivKeys [notaryCount]*ecdsa.PrivateKey
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initializes back end with 10000 accounts and each with 2000 eth balances
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
notaryPoolAddr[i] = crypto.PubkeyToAddress(key.PublicKey)
txOpts[i] = bind.NewKeyedTransactor(key)
txOpts[i].Value = notaryDeposit
genesis[notaryPoolAddr[i]] = core.GenesisAccount{
Balance: accountBalance2000Eth,
}
}
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10000 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 {
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() {
t.Errorf("Shard 0 committee list is identical to shard 1's committee list")
}
}
}
func TestGetCommitteeWithNonMember(t *testing.T) {
const notaryCount = 11
var notaryPoolAddr [notaryCount]common.Address
var notaryPoolPrivKeys [notaryCount]*ecdsa.PrivateKey
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initialize back end with 11 accounts and each with 2000 eth balances
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
notaryPoolAddr[i] = crypto.PubkeyToAddress(key.PublicKey)
txOpts[i] = bind.NewKeyedTransactor(key)
txOpts[i].Value = notaryDeposit
genesis[notaryPoolAddr[i]] = core.GenesisAccount{
Balance: accountBalance2000Eth,
}
}
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10 notaries to SMC, leave 1 address free
for i := 0; i < 10; i++ {
smc.RegisterNotary(txOpts[i])
backend.Commit()
}
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(10)) != 0 {
t.Fatalf("Incorrect count from notary pool. Want: 135, Got: %v", numNotaries)
}
// 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() {
t.Errorf("Account %s is not a notary", notaryPoolAddr[10].String())
}
}
}
func TestGetCommitteeAfterDeregisters(t *testing.T) {
const notaryCount = 10
var notaryPoolAddr [notaryCount]common.Address
var notaryPoolPrivKeys [notaryCount]*ecdsa.PrivateKey
var txOpts [notaryCount]*bind.TransactOpts
genesis := make(core.GenesisAlloc)
// initialize back end with 10 accounts and each with 2000 eth balances
for i := 0; i < notaryCount; i++ {
key, _ := crypto.GenerateKey()
notaryPoolPrivKeys[i] = key
notaryPoolAddr[i] = crypto.PubkeyToAddress(key.PublicKey)
txOpts[i] = bind.NewKeyedTransactor(key)
txOpts[i].Value = notaryDeposit
genesis[notaryPoolAddr[i]] = core.GenesisAccount{
Balance: accountBalance2000Eth,
}
}
backend := backends.NewSimulatedBackend(genesis)
_, _, smc, _ := deploySMCContract(backend, notaryPoolPrivKeys[0])
// register 10 notaries to SMC
for i := 0; i < 10; i++ {
smc.RegisterNotary(txOpts[i])
backend.Commit()
}
numNotaries, _ := smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(10)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 10, Got: %v", numNotaries)
}
// deregister notary 0 from SMC
txOpts[0].Value = big.NewInt(0)
smc.DeregisterNotary(txOpts[0])
backend.Commit()
numNotaries, _ = smc.NotaryPoolLength(&bind.CallOpts{})
if numNotaries.Cmp(big.NewInt(9)) != 0 {
t.Errorf("Incorrect count from notary pool. Want: 9, Got: %v", numNotaries)
}
// 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() {
t.Errorf("Account %s is not a notary", notaryPoolAddr[0].String())
}
}
}

View File

@ -1,4 +1,4 @@
package collator
package notary
import (
"context"
@ -14,7 +14,7 @@ import (
)
// SubscribeBlockHeaders checks incoming block headers and determines if
// we are an eligible collator for collations. Then, it finds the pending tx's
// we are an eligible notary for collations. Then, it finds the pending tx's
// from the running geth node and sorts them by descending order of gas price,
// eliminates those that ask for too much gas, and routes them over
// to the SMC to create a collation
@ -31,17 +31,17 @@ func subscribeBlockHeaders(c client.Client) error {
for {
// TODO: Error handling for getting disconnected from the client
head := <-headerChan
// Query the current state to see if we are an eligible collator
// Query the current state to see if we are an eligible notary
log.Info(fmt.Sprintf("Received new header: %v", head.Number.String()))
// Check if we are in the collator pool before checking if we are an eligible collator
v, err := isAccountInCollatorPool(c)
// Check if we are in the notary pool before checking if we are an eligible notary
v, err := isAccountInNotaryPool(c)
if err != nil {
return fmt.Errorf("unable to verify client in collator pool. %v", err)
return fmt.Errorf("unable to verify client in notary pool. %v", err)
}
if v {
if err := checkSMCForCollator(c, head); err != nil {
if err := checkSMCForNotary(c, head); err != nil {
return fmt.Errorf("unable to watch shards. %v", err)
}
} else {
@ -50,16 +50,16 @@ func subscribeBlockHeaders(c client.Client) error {
}
}
// checkSMCForCollator checks if we are an eligible collator for
// checkSMCForNotary checks if we are an eligible notary for
// collation for the available shards in the SMC. The function calls
// getEligibleCollator from the SMC and collator a collation if
// getEligibleNotary from the SMC and notary a collation if
// conditions are met
func checkSMCForCollator(c client.Client, head *types.Header) error {
log.Info("Checking if we are an eligible collation collator for a shard...")
func checkSMCForNotary(c client.Client, head *types.Header) error {
log.Info("Checking if we are an eligible collation notary for a shard...")
period := big.NewInt(0).Div(head.Number, big.NewInt(sharding.PeriodLength))
for s := int64(0); s < sharding.ShardCount; s++ {
// Checks if we are an eligible collator according to the SMC
addr, err := c.SMCCaller().GetEligibleCollator(&bind.CallOpts{}, big.NewInt(s), period)
// Checks if we are an eligible notary according to the SMC
addr, err := c.SMCCaller().GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(s), period)
if err != nil {
return err
@ -67,10 +67,19 @@ func checkSMCForCollator(c client.Client, head *types.Header) error {
// If output is non-empty and the addr == coinbase
if addr == c.Account().Address {
log.Info(fmt.Sprintf("Selected as collator on shard: %d", s))
log.Info(fmt.Sprintf("Selected as notary on shard: %d", s))
err := submitCollation(s)
if err != nil {
return fmt.Errorf("could not add collation. %v", err)
return err
}
// If the account is selected as collator, submit collation
if addr == c.Account().Address {
log.Info(fmt.Sprintf("Selected as collator on shard: %d", s))
err := submitCollation(s)
if err != nil {
return fmt.Errorf("could not add collation. %v", err)
}
}
}
}
@ -78,18 +87,18 @@ func checkSMCForCollator(c client.Client, head *types.Header) error {
return nil
}
// isAccountInCollatorPool checks if the client is in the collator pool because
// isAccountInNotaryPool checks if the client is in the notary pool because
// we can't guarantee our tx for deposit will be in the next block header we receive.
// The function calls IsCollatorDeposited from the SMC and returns true if
// the client is in the collator pool
func isAccountInCollatorPool(c client.Client) (bool, error) {
// The function calls IsNotaryDeposited from the SMC and returns true if
// the client is in the notary pool
func isAccountInNotaryPool(c client.Client) (bool, error) {
account := c.Account()
// Checks if our deposit has gone through according to the SMC
b, err := c.SMCCaller().IsCollatorDeposited(&bind.CallOpts{}, account.Address)
if !b && err != nil {
log.Warn(fmt.Sprintf("Account %s not in collator pool.", account.Address.String()))
n, err := c.SMCCaller().NotaryRegistry(&bind.CallOpts{}, account.Address)
if !n.Deposited && err != nil {
log.Warn(fmt.Sprintf("Account %s not in notary pool.", account.Address.String()))
}
return b, err
return n.Deposited, err
}
// submitCollation interacts with the SMC directly to add a collation header
@ -122,19 +131,19 @@ func submitCollation(shardID int64) error {
return nil
}
// joinCollatorPool checks if the account is a collator in the SMC. If
// joinNotaryPool checks if the account is a notary in the SMC. If
// the account is not in the set, it will deposit ETH into contract.
func joinCollatorPool(c client.Client) error {
func joinNotaryPool(c client.Client) error {
log.Info("Joining collator pool")
log.Info("Joining notary pool")
txOps, err := c.CreateTXOpts(sharding.NotaryDeposit)
if err != nil {
return fmt.Errorf("unable to intiate the deposit transaction: %v", err)
}
tx, err := c.SMCTransactor().Deposit(txOps)
tx, err := c.SMCTransactor().RegisterNotary(txOps)
if err != nil {
return fmt.Errorf("unable to deposit eth and become a collator: %v", err)
return fmt.Errorf("unable to deposit eth and become a notary: %v", err)
}
log.Info(fmt.Sprintf("Deposited %dETH into contract with transaction hash: %s", new(big.Int).Div(sharding.NotaryDeposit, big.NewInt(params.Ether)), tx.Hash().String()))

View File

@ -0,0 +1,41 @@
// Package notary holds all of the functionality to run as a notary in a sharded system.
package notary
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/sharding/client"
cli "gopkg.in/urfave/cli.v1"
)
// Notary runnable client.
type Notary interface {
// Start the main routine for a notary.
Start() error
}
type notary struct {
client client.Client
}
// NewNotary creates a new notary instance.
func NewNotary(ctx *cli.Context) Notary {
return &notary{
client: client.NewClient(ctx),
}
}
// Start the main routine for a notary.
func (c *notary) Start() error {
log.Info("Starting notary client")
err := c.client.Start()
if err != nil {
return err
}
defer c.client.Close()
if err := joinNotaryPool(c.client); err != nil {
return err
}
return subscribeBlockHeaders(c.client)
}

View File

@ -1,4 +1,4 @@
package collator
package notary
import (
"math/big"
@ -20,7 +20,7 @@ var (
accountBalance1001Eth, _ = new(big.Int).SetString("1001000000000000000000", 10)
)
// Mock client for testing collator. Should this go into sharding/client/testing?
// Mock client for testing notary. Should this go into sharding/client/testing?
type mockClient struct {
smc *contracts.SMC
t *testing.T
@ -59,7 +59,7 @@ func (m *mockClient) Close() {
}
// Helper/setup methods
// TODO: consider moving these to common sharding testing package as the collator and smc tests
// TODO: consider moving these to common sharding testing package as the notary and smc tests
// use them.
func transactOpts() *bind.TransactOpts {
return bind.NewKeyedTransactor(key)
@ -71,60 +71,60 @@ func setup() (*backends.SimulatedBackend, *contracts.SMC) {
return backend, smc
}
func TestIsAccountInCollatorPool(t *testing.T) {
func TestIsAccountInNotaryPool(t *testing.T) {
backend, smc := setup()
client := &mockClient{smc: smc, t: t}
// address should not be in pool initially
b, err := isAccountInCollatorPool(client)
b, err := isAccountInNotaryPool(client)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("Account unexpectedly in collator pool")
t.Fatal("Account unexpectedly in notary pool")
}
txOpts := transactOpts()
// deposit in collator pool, then it should return true
// deposit in notary pool, then it should return true
txOpts.Value = sharding.NotaryDeposit
if _, err := smc.Deposit(txOpts); err != nil {
if _, err := smc.RegisterNotary(txOpts); err != nil {
t.Fatalf("Failed to deposit: %v", err)
}
backend.Commit()
b, err = isAccountInCollatorPool(client)
b, err = isAccountInNotaryPool(client)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("Account not in collator pool when expected to be")
t.Fatal("Account not in notary pool when expected to be")
}
}
func TestJoinCollatorPool(t *testing.T) {
func TestJoinNotaryPool(t *testing.T) {
backend, smc := setup()
client := &mockClient{smc, t}
// There should be no collators initially
numCollators, err := smc.NumCollators(&bind.CallOpts{})
// There should be no notary initially
numNotaries, err := smc.NotaryPoolLength(&bind.CallOpts{})
if err != nil {
t.Fatal(err)
}
if big.NewInt(0).Cmp(numCollators) != 0 {
t.Fatalf("Unexpected number of collators. Got %d, wanted 0.", numCollators)
if big.NewInt(0).Cmp(numNotaries) != 0 {
t.Fatalf("Unexpected number of notaries. Got %d, wanted 0.", numNotaries)
}
err = joinCollatorPool(client)
err = joinNotaryPool(client)
if err != nil {
t.Fatal(err)
}
backend.Commit()
// Now there should be one collator
numCollators, err = smc.NumCollators(&bind.CallOpts{})
// Now there should be one notary
numNotaries, err = smc.NotaryPoolLength(&bind.CallOpts{})
if err != nil {
t.Fatal(err)
}
if big.NewInt(1).Cmp(numCollators) != 0 {
t.Fatalf("Unexpected number of collators. Got %d, wanted 1.", numCollators)
if big.NewInt(1).Cmp(numNotaries) != 0 {
t.Fatalf("Unexpected number of notaries. Got %d, wanted 1.", numNotaries)
}
}

1
sharding/shard.go Normal file
View File

@ -0,0 +1 @@
package sharding