Merge branch 'master' into smc_phase1

Former-commit-id: 3499684490b234ed887834cfdc36e5d207d260a7 [formerly f2d74d477d17afb2871e5997584ab7ea542f99ee]
Former-commit-id: ce4db3f2fa9925f3a31454d6d0058a1a4c407adb
This commit is contained in:
Enrique Fynn
2018-04-03 09:55:29 +02:00
committed by GitHub
16 changed files with 497 additions and 513 deletions

View File

@@ -100,8 +100,6 @@ Now, save the passphrase you used in the geth node into a text file called passw
Work in Progress. To track our current draft of the tx generator cli spec, visit this [link](https://docs.google.com/document/d/1YohsW4R9dIRo0u5RqfNOYjCkYKVCmzjgoBDBYDdu5m0/edit?usp=drive_web&ouid=105756662967435769870).
Once we have test transactions broadcast to our local node, we can start a collator and proposer client in separate terminal windows to begin the sharding process.
## Becoming a Collator
To deposit ETH and join as a collator in the Sharding Manager Contract, run the following command:
@@ -110,19 +108,18 @@ To deposit ETH and join as a collator in the Sharding Manager Contract, run the
geth sharding-collator --deposit --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
```
This will extract 100ETH from your account balance and insert you into the SMC's collator pool. Then, the program will listen for incoming block headers and notify you when you have been selected as an eligible collator for a certain shard in a given period. Once you are selected, the collator will request collations from a "proposals pool" that is created by a proposer node. We will need to run a proposer node concurrently in a separate terminal window as follows:
This will extract 100ETH from your account balance and insert you into the SMC's collator pool. Then, the program will listen for incoming block headers and notify you when you have been selected as an eligible collator for a certain shard in a given period. Once you are selected, the collator will request collations created by proposer nodes. We will need to run a proposer node concurrently in a separate terminal window as follows:
## Becoming a Proposer
```
geth sharding-proposer --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
```
Proposers are tasked with processing pending transactions into blobs within collations. They are responsible for submitting proposals (collation headers) to collators currently assigned to a period along with an ETH bid.
Proposers are tasked with state execution, so they will process and validate pending transactions in the Geth node and create collations with headers that are then broadcast to a proposals pool along with an ETH deposit.
Collators then subscribe to incoming proposals and fetch the collation headers that offer the highest ETH deposit. Once a collator issues a commitment to a certain proposal, the proposer can be confident the collator will download the body. After this countersigning occurs, the collator can add the collation header to the Sharding Manager Contract.
Collators then subscribe to changes in the proposals pool and fetch the collation headers that offer the highest ETH deposit. Once a collator signs this collation, the proposer needs to provide the full collation body and the collator can then append the collation header to the Sharding Manager Contract.
Once this is done, the full, end-to-end sharding example is complete and another iteration can occur.
Once this is done, the full, end-to-end phase 1 sharding example is complete and another iteration can occur.
# Making Changes

View File

@@ -1,9 +1,9 @@
package main
import (
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/collator"
"github.com/ethereum/go-ethereum/sharding/proposer"
"fmt"
"github.com/ethereum/go-ethereum/cmd/utils"
cli "gopkg.in/urfave/cli.v1"
)
@@ -36,15 +36,11 @@ Launches a sharding proposer client that connects to a running geth node and pro
)
func collatorClient(ctx *cli.Context) error {
c := sharding.MakeCollatorClient(ctx)
if err := c.Start(); err != nil {
return err
}
c.Wait()
return nil
c := collator.NewCollator(ctx)
return c.Start()
}
func proposerClient(ctx *cli.Context) error {
fmt.Println("Starting proposer client")
return nil
p := proposer.NewProposer(ctx)
return p.Start()
}

View File

@@ -15,9 +15,8 @@ This document serves as a main reference for Prysmatic Labs' sharding implementa
- [System Start and User Entrypoint](#system-start-and-user-entrypoint)
- [The Sharding Manager Contract](#the-sharding-manager-contract)
- [Necessary Functionality](#necessary-functionality)
- [Depositing ETH and Becoming a Collator](#depositing-eth-and-becoming-a-collator)
- [Registering Collators and Proposers](#registering-collators-and-proposers)
- [Determining an Eligible Collator for a Period on a Shard](#determining-an-eligible-collator-for-a-period-on-a-shard)
- [Withdrawing From the Collator Set](#withdrawing-from-the-collator-set)
- [Processing and Verifying a Collation Header](#processing-and-verifying-a-collation-header)
- [Collator Sampling](#collator-sampling)
- [Collation Header Approval](#collation-header-approval)
@@ -25,7 +24,7 @@ This document serves as a main reference for Prysmatic Labs' sharding implementa
- [The Collator Client](#the-collator-client)
- [Local Shard Storage](#local-shard-storage)
- [The Proposer Client](#the-proposer-client)
- [Collation Headers and State Execution](#collation-headers-and-state-execution)
- [Collation Headers](#collation-headers)
- [Peer Discovery and Shard Wire Protocol](#peer-discovery-and-shard-wire-protocol)
- [Protocol Modifications](#protocol-modifications)
- [Protocol Primitives: Collations, Blocks, Transactions, Accounts](#protocol-primitives-collations-blocks-transactions-accounts)
@@ -60,7 +59,7 @@ This document serves as a main reference for Prysmatic Labs' sharding implementa
# Sharding Introduction
Currently, every single node running the Ethereum network has to process every single transaction that goes through the network. This gives the blockchain a high amount of security because of how much validation goes into each block, but at the same time it means that an entire blockchain is only as fast as its individual nodes and not the sum of their parts. Currently, transactions on the EVM are not parallelizable, and every transaction is executed in sequence globally. The scalability problem then has to do with the idea that a blockchain can have at most 2 of these 3 properties: decentralization, security, and scalability.
Currently, every single node running the Ethereum network has to process every single transaction that goes through the network. This gives the blockchain a high amount of security because of how much validation goes into each block, but at the same time it means that an entire blockchain is only as fast as its individual nodes and not the sum of its parts. Currently, transactions on the EVM are non-parallelizable, and every transaction is executed in sequence globally. The scalability problem then has to do with the idea that a blockchain can have at most 2 of these 3 properties: decentralization, security, and scalability.
If we have scalability and security, it would mean that our blockchain is centralized and that would allow it to have a faster throughput. Right now, Ethereum is decentralized and secure, but not scalable.
@@ -68,32 +67,30 @@ An approach to solving the scalability trilemma is the idea of blockchain shardi
## Basic Sharding Idea and Design
A sharded blockchain system is made possible by having nodes store “signed metadata” in the main chain of latest changes within each shard chain. Through this, we manage to create a layer of abstraction that tells us enough information about the global, synced state of parallel shard chains. These messages are called **collation headers**, which are specific structures that encompass important information about the chainstate of a shard in question. Collations are created by actors known as **proposer nodes** that are randomly tasked into packaging transactions and “selling” them to collator nodes that are then tasked into adding these collations into particular shards through a **proof of stake** system in a designated period of time.
A sharded blockchain system is made possible by having nodes store “signed metadata” in the main chain of latest changes within each shard chain. Through this, we manage to create a layer of abstraction that tells us enough information about the global, synced state of parallel shard chains. These messages are called **collation headers**, which are specific structures that encompass important information about the chainstate of a shard in question. Collations are created by actors known as **proposer nodes** that are tasked with packaging transactions and “offering” them to collator nodes through cryptoeconomic incentive mechanisms. These collators are randomly selected for particular periods of time and are then tasked into adding these collations into particular shards through a **proof of stake** system occurring through a smart contract on the Ethereum main chain.
These collations are holistic descriptions of the state and transactions on a certain shard. A collation header contains the following information:
These collations are holistic descriptions of the state and transactions on a certain shard. A collation header at its most basic, high level summary contains the following information:
- Information about what shard the collation corresponds to (lets say shard 10)
- Information about the current state of the shard before all transactions are applied
- Information about what the state of the shard will be after all transactions are applied
- Information about the cryptoeconomic incentive game the successful collator and proposer played
For detailed information on protocol primitives including collations, see: [Protocol Primitives](#protocol-primitives). We will have two types of nodes that do the heavy lifting of our sharding logic: **proposers and collators**. The basic role of proposers is to fetch pending transactions from the txpool, execute any state logic or computation, wrap them into collations, and submit them along with an ETH deposit to a **proposals pool**.
For detailed information on protocol primitives including collations, see: [Protocol Primitives](#protocol-primitives). We will have two types of nodes that do the heavy lifting of our sharding logic: **proposers and collators**. The basic role of proposers is to fetch pending transactions from the txpool, wrap them into collations, and submit them along with an ETH deposit to a **proposals pool**.
<!--[Proposer{bg:wheat}]fetch txs-.->[TXPool], [TXPool]-.->[Proposer{bg:wheat}], [Proposer{bg:wheat}]-package txs>[Collation|header|ETH Deposit], [Collation|header|ETH Deposit]-submit>[Proposals Pool], [Collator{bg:wheat}]subscribe to-.->[Proposals Pool]-->
![proposers](https://yuml.me/6da583d7.png)
Collators add collations in the proof of work chain, throughout the document named the **canonical chain**. Collators subscribe to updates in the proposals pool and pick a collation in their best interest. Once collators are selected to add collations to the canonical chain, and do so successfully, they get paid by the deposit the proposer offered.
Collators add collations via a smart contract on the Ethereum mainchain. Collators subscribe to updates from proposers and pick a collation that offers them the highest payout, known as a **proposer bid**. Once collators are selected to add collations to the canonical chain, and do so successfully, they get paid by the deposit the proposer offered.
To recap, the role of a collator is to reach consensus through Proof of Stake on collations they receive in the period they are assigned to. This consensus will involve validation and data availability proofs of collations proposed to them by proposer nodes, along with validating collations from the immediate past (See: [Windback](#enforced-windback)).
To recap, the role of a collator is to reach consensus through Proof of Stake on collations they receive in the period they are assigned to, as well as to determine the canonical shard chain via a process known as "windback". This consensus will involve validation and data availability proofs of collations proposed to them by proposer nodes, along with validating collations from the immediate past (See: [Windback](#enforced-windback)).
When processing collations, proposer nodes download the merkle branches of the state that transactions within their collations need. In the case of cross-shard transactions, an access list of the state along with transaction receipts are required as part of the transaction primitive (See: [Protocol Primitives](#protocol-primitives)). Additionally, these proposers need to provide proofs of availability and validity when submitting collations for “sale” to collators. This submission process is akin to the current transaction fee open bidding market where miners accept the transactions that maximize their profits. This abstract separation of concerns between collators and proposers allows for more computational efficiency within the system, as collators will not have to do the heavy lifting of state execution and focus solely on consensus through fork-choice rules.
So then, are proposers in charge of state execution? The short answer is that phase 1 will contain **no state execution**. Instead, proposers will simply package all types of transactions into collations and later down the line, agents known as executors will download, run, and validate state as they need to through possibly different types of execution engines (potentially TrueBit-style, interactive execution).
The proposal-collator interaction is akin to the current transaction fee open bidding market where miners accept the transactions that maximize their profits. This abstract separation of concerns between collators and proposers allows for more computational efficiency within the system, as collators will not have to do the heavy lifting of state execution and focus solely on consensus through fork-choice rules. In this scheme, it makes sense that eventually **proposers** will become **executors** in later phases of a sharding spec.
When deciding and signing a proposed, valid collation, collators have the responsibility of finding the **longest valid shard chain within the longest valid main chain**.
In this new protocol, a block is valid when
- Transactions in all collations are valid
- The state of collations after the transactions is the same as what the collation headers specified
Collators periodically get assigned to different shards, the moment between when collators get assigned to a shard and the moment they get reassigned is called a **period**.
Given that we are splitting up the global state of the Ethereum blockchain into shards, new types of attacks arise because fewer hash power is required to completely dominate a shard. This is why a **source of randomness**, and periods are critical components to ensuring the integrity of the system.
@@ -102,18 +99,38 @@ The Ethereum Wikis [Sharding FAQ](https://github.com/ethereum/wiki/wiki/Shard
Casper Proof of Stake (Casper [FFG](https://arxiv.org/abs/1710.09437) and [CBC](https://arxiv.org/abs/1710.09437)) makes this quite trivial because there is already a set of global collators that we can select collator nodes from. The source of randomness needs to be common to ensure that this sampling is entirely compulsory and cant be gamed by the collators in question.
In practice, the first phase of sharding will not be a complete overhaul of the network, but rather an implementation through a smart contract on the main chain known as the **Sharding Manager Contract (SMC)**. Its responsibility is to manage shards and sampling proposed collators from a global collator set. As the SMC lives in the canonical chain, it will take guarantee a global state among all shard states.
In practice, the first phase of sharding will not be a complete overhaul of the network, but rather an implementation through a smart contract on the main chain known as the **Sharding Manager Contract (SMC)**. Its responsibility is to manage proposers, their proposal bids, and the sampling of collators from a global collator set. As the SMC lives in the Ethereum main chain, it will take guarantee a canonical head for all shard states.
Among its basic responsibilities, the SMC is be responsible for reconciling collators across all shards. It is in charge of pseudorandomly sampling collators from a collator set of accounts that have staked ETH into the SMC. The SMC is also responsible for providing immediate collation header verification that records a valid collation header hash on the canonical chain. In essence, sharding revolves around being able to store proofs of shard states in the canonical chain through this smart contract.
Among its basic responsibilities, the SMC is be responsible for reconciling collators across all shards. It is in charge of pseudorandomly sampling collators from a collator set of accounts that have staked ETH into the SMC. The SMC is also responsible for providing immediate collation header verification that records a valid collation header hash on the main chain, as well as managing proposer's bids. In essence, sharding revolves around being able to store proofs of shard states in the main chain through this smart contract.
# Roadmap Phases
Prysmatic Labs implementation will follow parts of the roadmap outlined by Vitalik in his [Sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ) to roll out a working version of quadratic sharding, with a few modifications on our releases.
Prysmatic Labs will follow the updated Phase 1 Spec posted on [ETHResearch](https://ethresear.ch/t/sharding-phase-1-spec/1407) by the Foundation's research team to roll out a local version of qudratic sharding. In essence, the high-level sharding roadmap is as follows as outlined by Justin Drake:
1. **Phase 1:** Basic SMC shard system with no cross-shard communication along with a proposer + collator node architecture
2. **Phase 2:** Receipt-based, cross-shard communication
3. **Phase 3:** Require collation headers to be added in as uncles instead of as transactions
4. **Phase 4:** Tightly-coupled sharding with data availability proofs and robust security
- Phase 1: Basic sharding without EVM
- Blob shard without transactions
- Proposers
- Proposal commitments
- Collation availability challenges
- Phase 2: EVM state transition function
- Full nodes only
- Asynchronous cross-contract calls only
- Account abstraction
- eWASM
- Archive accumulators
- Storage rent
- Phase 3: Light client state protocol
- Executors
- Stateless clients
- Phase 4: Cross-shard transactions
- Internally-synchronous zones
- Phase 5: Tight coupling with main chain security
- Data availability proofs
- Casper integration
- Internally fork-free sharding
- Manager shard
- Phase 6: Super-quadratic sharding
- Load balancing
To concretize these phases, we will be releasing our implementation of sharding for the geth client as follows:
@@ -121,13 +138,14 @@ To concretize these phases, we will be releasing our implementation of sharding
Our current work is focused on creating a localized version of phase 1, quadratic sharding that would include the following:
- A minimal, **collator client** system that will deploy a **Sharding Manager Contract** to a locally running geth node
- A minimal, **collator client** system that will interact with a **Sharding Manager Contract** on a locally running geth node
- Ability to deposit ETH into the SMC through the command line and to be selected as a collator by the local **SMC** in addition to the ability to withdraw the ETH staked
- A **proposer node client** and Cryptoeconomic incentive system for proposer nodes to listen for pending txs, create collations, and submit them along with a deposit to collator nodes in the network
- A simple command line util to **simulate pending transactions** of different types posted to the local geth nodes txpool for the local collation proposer to begin proposing collation headers
- A **proposer client** and cryptoeconomic incentive system for proposer nodes to listen for pending txs, create collations, and submit them along with a deposit to collator nodes in the network
- Ability to inspect the shard states and visualize the working system locally through the command line
We will forego many of the security considerations that will be critical for testnet and mainnet release for the purposes of demonstration and local network execution as part of the Ruby Release (See: [Security Considerations Not Included in Ruby](#not-included-in-ruby-release)).
Proposers and collators will interact through a local file system, as peer to peer networking considerations for sharding are still under heavy research.
We will forego several security considerations that will be critical for testnet and mainnet release for the purposes of demonstration and local network testing as part of the Ruby Release (See: [Security Considerations Not Included in Ruby](#not-included-in-ruby-release)).
ETA: To be determined
@@ -135,7 +153,7 @@ ETA: To be determined
Part 1 of the **Sapphire Release** will focus around getting the **Ruby Release** polished enough to be live on an Ethereum testnet and manage a set of collators effectively processing collations through the **on-chain SMC**. This will require a lot more elaborate simulations around the safety of the randomness behind the collator assignments in the SMC. Futhermore we need to pass stress testing against DDoS and other sorts of byzantine attacks. Additionally, it will be the first release to have real users proposing collations concurrently along with collators that can accept these proposals and add their headers to the SMC.
Part 2 of the **Sapphire Release** will focus on implementing a cross-shard transaction mechanism via two-way pegging and the receipts system (as outlined in [Beyond Phase 1](#beyond-phase-1)) and getting that functionality ready to run on a **local, private network** as an extension to the Ruby Release.
Part 2 of the **Sapphire Release** will focus on implementing state execution and defining the State Transition Function for sharding on a local testnet (as outlined in [Beyond Phase 1](#beyond-phase-1)) as an extenstion to the Ruby Release.
ETA: To be determined
@@ -155,29 +173,32 @@ Here is a full reference spec explaining how our initial system will function:
## System Architecture
Our implementation revolves around 5 core components:
Our implementation revolves around 4 core components:
- A **locally-running geth node** that spins up an instance of the Ethereum blockchain
- A **locally-running geth node** that spins up an instance of the Ethereum blockchain and mines on the Proof of Work chain
- A **Sharding Manager Contract (SMC)** that is deployed onto this blockchain instance
- A **collator client** that connects to the running geth node through JSON-RPC, provides bindings to the SMC, and listens for incoming collation proposals
- A **proposer client** that is tasked with state execution, processing pending txs from the Geth node, and creates collations that are then broadcast to collators via a wire protocol
- A **user** that will interact with the sharding client to become a collator and automatically process transactions into shards through the sharding clients SMC bindings.
- A **proposer client** that is tasked with processing pending tx's into collations that are then submitted to collators via a local filesystem for the purposes of simplified, phase 1 implementation. In phase 1, proposers _do not_ execute state, but rather just serialize pending tx data into possibly valid/invalid data blobs.
Our initial implementation will function through simple command line arguments that will allow a user running the local geth node to deposit ETH into the SMC and join as a collator that is automatically assigned to a certain period. We will also launch a proposer client that will create collations from the geth nodes tx pool and submit them to collators for them to add their headers to the SMC.
Our initial implementation will function through simple command line arguments that will allow a user running the local geth node to deposit ETH into the SMC and join as a collator that is automatically assigned to a certain period. We will also launch a proposer client that will create collations and submit them to collators for them to add their headers to the SMC via a cryptoeconomic "game".
A basic, end-to-end example of the system is as follows:
1. _**A User starts a collator client and deposits 100ETH into the SMC:**_ the sharding client connects to a locally running geth node and asks the user to confirm a deposit from his/her personal account.
1. _**User starts a collator client and deposits 100ETH into the SMC:**_ the sharding client connects to a locally running geth node and asks the user to confirm a deposit from his/her personal account.
2. _**Collator client connects & listens to incoming headers from the geth node and assigns user as collators on a shard per period:**_ The collator is selected for the current period and must accept collations from proposer nodes that offer the best prices.
2. _**Collator client connects & listens to incoming headers from the geth node and assigns user as collators on a shard per period:**_ The collator is selected in CURRENT_PERIOD + LOOKEAD_PERIOD (which is around a 5 minutes notice) and must accept collations from proposer nodes that offer the best prices.
3. _**Concurrently, the proposer client processes and executes pending txs from the geth node:**_ the proposer client will create valid collations and submit them to collators on an open bidding system.
3. _**Concurrently, a proposer client processes pending transaction data into blobs:**_ the proposer client will create collation bodies and submit them to collators through an open bidding system. In Phase 1, it is important to note that we will _not_ have any state execution. Proposers will just serialize pending tx into fixed collation body sizes without executing them for validity.
4. _**Collators select collation proposals that offer highest payout:**_ Collators listen to collation headers on a certain shard with high deposit sizes and sign them.
4. _**Collators broadcast a signed commitment to all top proposers:**_ collators create a signed list called a _commitment_ that specifies top collations he/she would accept based on a high payout. This eliminates some risk on behalf of proposers, who need some guarantee that they are not being led on by collators.
5. _**The collator adds collation headers through the SMC:**_ The collator client calls the `add_header` function in the SMC and to append the header to the canonical chain.
5. _**Top proposer in commitment broadcasts his/her collation body to the collator:** proposer sends the collation body to the collator, but does not have a way of ensuring the collator will not be lazy and not download the body.
6. _**The user is selected as collator again on the SMC in a different period or can withdraw his/her stake from the collator's Pool:**_ the user can keep staking and adding incoming collation headers and restart the process, or withdraw his/her stake and be removed from the SMC collator set.
4. _**Proposers can challenge collators on data availability:**_ to ease more proposer risk, we give proposers the ability to challenge the data availability of a collation downloaded by a collator. If collator cannot respond in a certain period of time, collator is slashed.
5. _**The collator adds collation headers through the SMC:**_ the collator client checks the previous 25 collations in the shard chain to check for validity, and then uses the fork choice rule to determine the canonical shard head the recently exchanged collation would be appended to via an `addHeader` SMC call.
6. _**User is selected as collator again on the SMC in a different period or can withdraw his/her stake from the collator's pool:**_ the user can keep staking and adding incoming collation headers and restart the process, or withdraw his/her stake and be removed from the SMC collator pool.
Now, well explore our architecture and implementation in detail as part of the go-ethereum repository.
@@ -189,11 +210,9 @@ Our Ruby Release requires users to start a local geth node running a localized,
$ geth sharding-collator --deposit --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
```
If it is the first time the client runs, it deploys a new **SMC** into the local chain and establish a JSON-RPC connection to interact with the node directly. The `--deposit` flag tells the sharding client to automatically unlock the users keystore and begin depositing ETH into the SMC to become a collator.
If it is the first time the client runs, it deploys a new **SMC** into the local chain and establishes a JSON-RPC connection to interact with the node directly. The `--deposit` flag tells the sharding client to automatically unlock the users keystore and begin depositing ETH into the SMC to become a collator.
If the initial deposit is successful, the sharding client launches a **local, transaction simulation generator**, which will queue transactions into the txpool for the geth node to process that can then be added into collations on a shard.
Concurrently, a user needs to launch a **proposer client** that starts processing transactions into collations that can then be “sold” to collators by including a cryptographic proof of an ETH deposit in their unsigned collation headers. This proof is a guarantee of a state change in the collators account balance for accepting to sign the incoming collation header. The proposer client can also be initialized as follows in a separate process:
Concurrently, a user needs to launch a **proposer client** that starts processing transactions into collations that can then be offered to collators by including an ETH deposit in their unsigned collation headers. The proposer client can also be initialized as follows in a separate process:
```
geth sharding-proposer --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
@@ -203,25 +222,22 @@ Back to the collators, the collator client begins to work by its main loop, whic
1. _**Subscribe to incoming block headers:**_ the client will begin by issuing a subscription over JSON-RPC for block headers from the running geth node.
2. _**Check shards for eligible collator:**_ On incoming headers, the client will interact with the SMC to check if the current collator is an eligible collator for upcoming periods (only a few minutes notice)
2. _**Check shards for eligible collator within LOOKEAD_PERIOD:**_ on incoming headers, the client will interact with the SMC to check if the current collator is an eligible collator for an upcoming period (only a few minutes notice)
3. _**If the collator is selected, fetch proposals from proposal nodes and add collation headers to SMC:**_ Once a collator is selected, he/she only has a small timeframe to add collation headers through the SMC, so he/she looks for proposals from proposer nodes and accepts those that offer the highest payouts. The collator then countersigns the collation header, receives the full collation with its body after signing, and adds it to the SMC through PoS consensus.
3. _**If the collator is selected, fetch proposals from proposal nodes and add collation headers to SMC:**_ once a collator is selected, he/she only has a small timeframe to add collation headers through the SMC, so he/she looks for proposals from proposer nodes and accepts those that offer the highest payouts. The collator then broadcasts a commitment that includes a cryptographic guarantee of the specific proposals such collator would accept if proposer publishes a corresponding collation body. Then, the top proposer that acts accordingly gets his/her proposal accepted, and the collator appends it to the SMC through a simplified PoS consensus.
4. _**Supernode reconciles and adds to main chain:**_ Supernodes that are responsible for reconciling global state across shards into the canonical chain. They are tasked with observing the state across the whole galaxy of shards and adding blocks to the canonical PoW main chain. Proposers get rewarded to their coinbase address for inclusion of a block (also known as a collation subsidy).
5. _**If user withdraws, remove from collator set:**_ a user can choose to stop being a collator and then his/her ETH is withdrawn from the collator set after a certain lockup period is met.
5. _**If user withdraws, remove from collator set:**_ A user can choose to stop being a collator and then his/her ETH is withdrawn from the collator set.
6. _**Otherwise, collating client keeps subscribing to block headers:**_ If the user chooses to keep going,
It will be the proposer clients responsibility to listen to any new broadcasted transactions to the node and interact with collators that have staked their ETH into the SMC through an open bidding system for collation proposals. Proposer clients are the ones responsible for **state execution** of transactions in the tx pool.
6. _**Otherwise, collating client keeps subscribing to block headers:**_ If the user chooses to keep going, it will be the proposer clients responsibility to listen to any new pending transactions and interact with collators that have staked their ETH into the SMC through an open bidding system for collation proposals.
<!--[Transaction Generator]generate test txs->[Shard TXPool],[Geth Node]-deploys>[Sharding Manager Contract{bg:wheat}], [Shard TXPool]<fetch pending txs-.->[Proposer Client], [Proposer Client]-propose collation>[Collator Client],[Collator Client]add collation header->[Sharding Manager Contract{bg:wheat}]-->
![system functioning](https://yuml.me/4a7c8c5b.png)
## The Collator Manager Contract
## The Sharding Manager Contract
Our solidity implementation of the Collator Manager Contract follows the reference spec outlined by Vitalik [here](https://github.com/ethereum/sharding/blob/develop/docs/doc.md).
Our solidity implementation of the Collator Manager Contract follows the reference spec outlined in ETHResearch's Updated Phase 1 Spec [here](https://ethresear.ch/t/sharding-phase-1-spec/1407).
### Necessary functionality
### Necessary Functionality
In our Solidity implementation, we begin with the following sensible defaults:
@@ -236,19 +252,59 @@ uint constant depositSize = 100 ether;
uint constant lookAheadPeriods = 4;
```
Then, the 4 minimal functions required by the SMC are as follows:
The contract will store the following data structures (full credit to the Phase 1 Spec)
#### Depositing ETH and Becoming a Collator
- Collator pool
- `collator_pool`: `address[int128]`—array of active collator addresses
- `collator_pool_len`: `int128`—size of the collator pool
- `empty_slots_stack`: `int128[int128]`—stack of empty collator slot indices
- `empty_slots_stack_top`: `int128`—top index of the stack
- Collator registry
- `collator_registry`: `{deregistered: int128, pool_index: int128}[address]`—collator registry (deregistered is 0 for not yet deregistered collators)
- Proposer registry
- `proposer_registry`: `{deregistered: int128, balances: wei_value[uint256]}[address]`—proposer registry
- Collation trees
- `collation_trees`: `bytes32[bytes32][uint256]`—collation trees (the collation tree of a shard maps collation hashes to previous collation hashes truncated to 24 bytes packed into a bytes32 with the collation height in the last 8 bytes)
- `last_update_periods`: `int128[uint256]`—period of last update for each shard
- Availability challenges
- `availability_challenges`:TBD—availability challenges
- `availability_challenges_len`: `int128`—availability challenges counter
Then, the minimal functions required by the SMC are as follows:
#### Registering Collators and Proposers
```javascript
function deposit() public payable returns(int) {
function registerCollator() public payable returns(bool) {
require(!isCollatorDeposited[msg.sender]);
require(msg.value == depositSize);
...
}
```
`deposit` adds a collator to the collator set, with the collator's size being the `msg.value` (i.e., the amount of ETH deposited) in the function call. This function returns the collator's index.
Locks a collator into the contract and updates the collator pool accordingly.
```javascript
function registerProposer() public payable returns(bool) {
require(!isCollatorDeposited[msg.sender]);
require(msg.value == depositSize);
...
}
```
Same as above but does **not** update the collator pool.
```javascript
function proposerAddBalance(uint256 shard_id) returns(bool) {
...
}
```
Adds `msg.value` to the balance of the proposer on `shard_id`, and returns `True` on success. Checks:
Shard: shard_id against NETWORK_ID and SHARD_COUNT
Authentication: `proposer_registry[msg.sender]` exists
#### Determining an Eligible Collator for a Period on a Shard
@@ -262,17 +318,6 @@ function getEligibleCollator(int _shardId, int _period) public view returns(addr
The `getEligibleCollator` function uses a block hash as a seed to pseudorandomly select a signer from the collator set. The chance of being selected should be proportional to the collator's deposit. The function should be able to return a value for the current period or any future up to `LOOKAHEAD_PERIODS` periods ahead.
#### Withdrawing From the collator Set
```javascript
function withdraw(int _collatorIndex) public {
require(msg.sender == collators[_collatorIndex].addr);
...
}
```
Authenticates the collator and removes him/her from the collator set, refunding the deposited ETH.
#### Processing and Verifying a Collation Header
```javascript
@@ -293,7 +338,9 @@ function addHeader(int _shardId, uint _expectedPeriodNumber, bytes32 _periodStar
The `addHeader` function is the most important function in the SMC as it provides on-chain verification of collation headers, and maintains a canonical ordering of processed collation headers.
Our current [solidity implementation](https://github.com/prysmaticlabs/geth-sharding/blob/master/sharding/contracts/sharding_manager.sol) includes all of these functions along with other utilities important for the our Ruby Release sharding scheme.
Our current [solidity implementation](https://github.com/prysmaticlabs/geth-sharding/blob/master/sharding/contracts/sharding_manager.sol) includes all of these functions along with other utilities important for the our Ruby Release sharding scheme.
For more details on these methods, please refer to the Phase 1 spec as it details all important requirements and additional functions to be included in the production-ready SMC.
### Collator Sampling
@@ -332,7 +379,7 @@ When we launch the client with
geth sharding-collator --deposit --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
```
The instance connects to a running geth node via JSON-RPC and calls the deposit function on a deployed, Sharding Manager Contract to insert the user into a collator set. Then, we subscribe for updates on incoming block headers and call `getEligibleCollator` on receiving each header. Once we are selected, our client fetches and “purchases” proposed, unsigned collations from a proposals pool created by proposer nodes. The collator client accepts a collation that offer the highest payout, countersigns it, and adds it to the SMC all within that period.
The instance connects to a running geth node via JSON-RPC and calls the deposit function on a deployed, Sharding Manager Contract to insert the user into a collator set. Then, we subscribe for updates on incoming block headers and call `getEligibleCollator` on receiving each header. Once we are selected within a LOOKAHEAD_PERIOD, our client fetches proposed, unsigned collations created by proposer nodes. The collator client accepts a collation that offer the highest payout, countersigns it, and adds it to the SMC.
### Local Shard Storage
@@ -342,7 +389,7 @@ Work in progress.
## The Proposer Client
In addition to launching a collator client, our system requires a user to concurrently launch a proposer client that is tasked with state execution, fetching pending txs from the running geth nodes txpool, and creating collations that can be sent to collators.
In addition to launching a collator client, our system requires a user to concurrently launch a proposer client that is tasked with fetching pending txs from the network and creating collations that can be sent to collators.
Users launch a proposal client as another geth entrypoint as follows:
@@ -350,11 +397,11 @@ Users launch a proposal client as another geth entrypoint as follows:
geth sharding-collator --datadir /path/to/your/datadir --password /path/to/your/password.txt --networkid 12345
```
Launching this command connects via JSON-RPC to fetch the geth nodes tx pool and see who the currently active collator node is for the period. The proposer is tasked with running transactions to create valid collations and executing their required computations, tracking used gas, and all the heavy lifting that is usually seen in full Ethereum nodes. Once a valid collation is created, the proposer broadcasts the unsigned header **(note: the body is not broadcasted)** to a proposals pool along with a guaranteed ETH deposit that is extracted from the proposers account upfront. Then, the current collator assigned for the period will find proposals for him/her assigned shard and sign the one with the highest payout.
Launching this command connects via JSON-RPC to give the client the ability to call required functions on the SMC. The proposer is tasked with packaging pending transaction data into _blobs_ and **not** executing these transactions. This is very important, we will not consider state execution until later phases of a sharding roadmap. The proposer broadcasts the unsigned header **(note: the body is not broadcasted)** to a proposals folder in the local, shared filesystem along with a guaranteed ETH deposit that is extracted from the proposers account upfront. Then, the current collator assigned for the period will find proposals for him/her assigned shard and sign the one with the highest payout.
Then, the collator node calls the addHeader function on the SMC by submitting this collation header. Well explore the structure of collation headers in this next section along with important considerations for state execution, as this can quickly become the bottleneck of the entire sharding system.
Then, the collator node calls the addHeader function on the SMC by submitting this collation header. Well explore the structure of collation headers in this next section.
### Collation Headers and State Execution
### Collation Headers
Work in progress.
@@ -404,9 +451,9 @@ In the sharding consensus mechanism, it is important to consider that we now hav
### Use-Case Stories: Proposers
The primary purpose of proposers is to use their computational power for state execution of transactions and create valid collations that can then be put on an open market for collators to take. Upon offering a proposal, proposers will deposit part of their ETH as a payout to the collator that adds its collation header to the SMC, __even if the collation gets orphaned__. By forcing proposers to take on this risk, we prevent a certain degree of collation proposal spamming, albeit not without a few other security problems: (See: [Active Research](#active-questions-and-research)).
The primary purpose of proposers is to package transaction data into collations that can then be put on an open market for collators to take. Upon offering a proposal, proposers will deposit part of their ETH as a payout to the collator that adds its collation header to the SMC, __even if the collation gets orphaned__. By forcing proposers to take on this risk, we prevent a certain degree of collation proposal spamming, albeit not without a few other security problems: (See: [Active Research](#active-questions-and-research)).
The primary incentive for proposers to generate these collations is to receive a payout to their coinbase address along with transactions fees from the ones they process once added to a block in the canonical chain.
The primary incentive for proposers to generate these collations is to receive a payout to their coinbase address along with transactions fees from the ones they process once added to a block in the canonical chain. This process, however, cannot occur until we have state execution in our protocol, so proposers will be running at a loss for our Phase 1 implementation.
### Use-Case Stories: Collators
@@ -430,9 +477,7 @@ You can track our progress, open issues, and projects in our repository [here](h
Under the uncoordinated majority model, in order to prevent a single shard takeover, random sampling is utilized. Each shard is assigned a certain number of collators. Collators that approve the collations on that shard are sampled randomly. However for the ruby release we will not be implementing any random sampling of the collators of the shard, as the primary objective of this release is to launch an archival sharding client which deploys the Sharding Management Contract to a locally running geth node.
Also for now we will not be implementing challenge response mechanisms to mitigate instances where malicious actors are penalized and have their staked slashed for making incorrect claims regarding the veracity of collations.
We will not be considering data availability proofs as part of the ruby release we will not be implementing them as it just yet as they are an area of active research.
We will not be considering data availability proofs (part of the stateless client model) as part of the ruby release we will not be implementing them as it just yet as they are an area of active research.
## Bribing, Coordinated Attack Models
@@ -509,7 +554,7 @@ To add validity to our current SMC spec, Drake mentions that we can use a succin
The other missing piece is the guarantee of data availability within collation headers submitted to the SMC which can once again be done through zero-knowledge proofs and erasure codes (See: The Data Availability Problem). By escalating this up to the SMC, we can ensure what Vitalik calls “tightly-coupled” sharding, in which case breaking a single shard would entail also breaking the progression of the canonical chain, enabling easier cross-shard communication due to having a single source of truth being the SMC and the associated collation headers it has processed. In Justin Drakes words, “there is no fork-choice rule within the SMC”.
It is important to note that this “tightly coupled” sharding has been relegated to Phase 4 of the roadmap.
It is important to note that this “tightly coupled” sharding has been relegated to the latter phases of the roadmap.
Work in progress.
@@ -521,7 +566,7 @@ In a recent [blog post](https://ethresear.ch/t/separating-proposing-and-confirma
This leads to significant computational burdens on collators that need to keep track of the state of a particular shard in the proposal process as part of the transaction packaging process. The potentially better approach outlined above is the idea of separating the transaction packaging process and the consensus mechanism into two separate nodes with different responsibilities. **Our model will be based on this separation and we will be releasing a proposer client alongside a collator client in our Ruby release**.
The two nodes would interact through a cryptoeconomic incentive system where proposers would package transactions and send unsigned collation headers (with obfuscated collation bodies) over to collators with a signed message including a deposit. If a collator chooses to accept the proposal, he/she would be rewarded by the amount specified in the deposit. This would allow proposers to focus all of their computational power solely on state execution and organizing transactions into proposals.
The two nodes would interact through a cryptoeconomic incentive system where proposers would package transactions and send unsigned collation headers (with obfuscated collation bodies) over to collators with a signed message including a deposit. If a collator chooses to accept the proposal, he/she would be rewarded by the amount specified in the deposit.
Along the same lines, it will make it easier for collators to constantly jump across shards to validate collations, as they no longer have the need for resyncing an entire state tree because they can simply receive collation proposals from proposer nodes. This is very important, as collator reshuffling is a crucial security consideration to prevent shard hostile takeovers.
@@ -533,7 +578,7 @@ In this system, collators would get paid the proposals deposit __even if the
In practice, we would end up with a system of 3 types of nodes to ensure the functioning of a sharded blockchain
1. Proposer nodes that are tasked with state execution and creation of unsigned collation headers, obfuscated collation bodies, data availability proofs, and an ETH deposit to be relayed to collators.
1. Proposer nodes that are tasked with the creation of unsigned collation headers, obfuscated collation bodies, and an ETH deposit to be relayed to collators.
2. Collator nodes that accept proposals through an open auction system similar to way transactions fees currently work. These nodes then sign these collations and pass them through the SMC for inclusion into a shard through PoS.
## Selecting Eligible Collators Off-Chain

View File

@@ -1,6 +1,7 @@
package sharding
//go:generate abigen --sol contracts/sharding_manager.sol --pkg contracts --out contracts/sharding_manager.go
// Package client provides an interface for interacting with a running ethereum full node.
// As part of the initial phases of sharding, actors will need access to the sharding management
// contract on the main PoW chain.
package client
import (
"bufio"
@@ -9,8 +10,9 @@ import (
"fmt"
"math/big"
"os"
"time"
"github.com/ethereum/go-ethereum"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@@ -22,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/contracts"
cli "gopkg.in/urfave/cli.v1"
)
@@ -30,17 +33,28 @@ const (
clientIdentifier = "geth" // Used to determine the ipc name.
)
// Client for Collator. Communicates to Geth node via JSON RPC.
type collatorClient struct {
endpoint string // Endpoint to JSON RPC
client *ethclient.Client // Ethereum RPC client.
keystore *keystore.KeyStore // Keystore containing the single signer
ctx *cli.Context // Command line context
smc *contracts.SMC // The deployed sharding management contract
// General client for Collator/Proposer. Communicates to Geth node via JSON RPC.
type shardingClient struct {
endpoint string // Endpoint to JSON RPC
client *ethclient.Client // Ethereum RPC client.
keystore *keystore.KeyStore // Keystore containing the single signer
ctx *cli.Context // Command line context
smc *contracts.SMC // The deployed sharding management contract
rpcClient *rpc.Client // The RPC client connection to the main geth node
}
// MakeCollatorClient for interfacing with Geth full node.
func MakeCollatorClient(ctx *cli.Context) *collatorClient {
type Client interface {
Start() error
Close()
CreateTXOps(*big.Int) (*bind.TransactOpts, error)
ChainReader() ethereum.ChainReader
Account() *accounts.Account
SMCCaller() *contracts.SMCCaller
SMCTransactor() *contracts.SMCTransactor
}
func NewClient(ctx *cli.Context) *shardingClient {
path := node.DefaultDataDir()
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
path = ctx.GlobalString(utils.DataDirFlag.Name)
@@ -64,24 +78,23 @@ func MakeCollatorClient(ctx *cli.Context) *collatorClient {
}
ks := keystore.NewKeyStore(keydir, scryptN, scryptP)
return &collatorClient{
return &shardingClient{
endpoint: endpoint,
keystore: ks,
ctx: ctx,
}
}
// Start the collator client.
// Start the sharding client.
// * Connects to Geth node.
// * Verifies or deploys the sharding manager contract.
func (c *collatorClient) Start() error {
log.Info("Starting collator client")
func (c *shardingClient) Start() error {
rpcClient, err := dialRPC(c.endpoint)
if err != nil {
return err
return fmt.Errorf("cannot start rpc client. %v", err)
}
c.rpcClient = rpcClient
c.client = ethclient.NewClient(rpcClient)
defer rpcClient.Close()
// Check account existence and unlock account before starting collator client
accounts := c.keystore.Accounts()
@@ -93,42 +106,22 @@ func (c *collatorClient) Start() error {
return fmt.Errorf("cannot unlock account. %v", err)
}
if err := initSMC(c); err != nil {
smc, err := initSMC(c)
if err != nil {
return err
}
c.smc = smc
// Deposit 100ETH into the collator set in the SMC. Checks if account
// is already a collator in the SMC (in the case the client restarted).
// Once that's done we can subscribe to block headers.
//
// TODO: this function should store the collator's SMC index as a property
// in the client's struct
if err := joinCollatorPool(c); err != nil {
return err
}
// Listens to block headers from the Geth node and if we are an eligible
// collator, we fetch pending transactions and collator a collation
if err := subscribeBlockHeaders(c); err != nil {
return err
}
return nil
}
// Wait until collator client is shutdown.
func (c *collatorClient) Wait() {
log.Info("Sharding client has been shutdown...")
}
// WatchCollationHeaders checks the logs for add_header func calls
// and updates the head collation of the client. We can probably store
// this as a property of the client struct
func (c *collatorClient) WatchCollationHeaders() {
// Close the RPC client connection
func (c *shardingClient) Close() {
c.rpcClient.Close()
}
// UnlockAccount will unlock the specified account using utils.PasswordFileFlag or empty string if unset.
func (c *collatorClient) unlockAccount(account accounts.Account) error {
func (c *shardingClient) unlockAccount(account accounts.Account) error {
pass := ""
if c.ctx.GlobalIsSet(utils.PasswordFileFlag.Name) {
@@ -152,7 +145,8 @@ func (c *collatorClient) unlockAccount(account accounts.Account) error {
return c.keystore.Unlock(account, pass)
}
func (c *collatorClient) createTXOps(value *big.Int) (*bind.TransactOpts, error) {
// CreateTXOps creates a *TransactOpts with a signer using the default account on the keystore.
func (c *shardingClient) CreateTXOps(value *big.Int) (*bind.TransactOpts, error) {
account := c.Account()
return &bind.TransactOpts{
@@ -169,27 +163,31 @@ func (c *collatorClient) createTXOps(value *big.Int) (*bind.TransactOpts, error)
}
// Account to use for sharding transactions.
func (c *collatorClient) Account() *accounts.Account {
func (c *shardingClient) Account() *accounts.Account {
accounts := c.keystore.Accounts()
return &accounts[0]
}
// ChainReader for interacting with the chain.
func (c *collatorClient) ChainReader() ethereum.ChainReader {
func (c *shardingClient) ChainReader() ethereum.ChainReader {
return ethereum.ChainReader(c.client)
}
// Client to interact with ethereum node.
func (c *collatorClient) Client() *ethclient.Client {
func (c *shardingClient) ethereumClient() *ethclient.Client {
return c.client
}
// SMCCaller to interact with the sharding manager contract.
func (c *collatorClient) SMCCaller() *contracts.SMCCaller {
func (c *shardingClient) SMCCaller() *contracts.SMCCaller {
return &c.smc.SMCCaller
}
func (c *shardingClient) SMCTransactor() *contracts.SMCTransactor {
return &c.smc.SMCTransactor
}
// dialRPC endpoint to node.
func dialRPC(endpoint string) (*rpc.Client, error) {
if endpoint == "" {
@@ -197,3 +195,45 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
}
return rpc.Dial(endpoint)
}
// initSMC initializes the sharding manager contract bindings.
// If the SMC does not exist, it will be deployed.
func initSMC(c *shardingClient) (*contracts.SMC, error) {
b, err := c.client.CodeAt(context.Background(), sharding.ShardingManagerAddress, nil)
if err != nil {
return nil, fmt.Errorf("unable to get contract code at %s: %v", sharding.ShardingManagerAddress, err)
}
// Deploy SMC for development only.
// TODO: Separate contract deployment from the sharding client. It would only need to be deployed
// once on the mainnet, so this code would not need to ship with the client.
if len(b) == 0 {
log.Info(fmt.Sprintf("No sharding manager contract found at %s. Deploying new contract.", sharding.ShardingManagerAddress.String()))
txOps, err := c.CreateTXOps(big.NewInt(0))
if err != nil {
return nil, fmt.Errorf("unable to intiate the transaction: %v", err)
}
addr, tx, contract, err := contracts.DeploySMC(txOps, c.client)
if err != nil {
return nil, fmt.Errorf("unable to deploy sharding manager contract: %v", err)
}
for pending := true; pending; _, pending, err = c.client.TransactionByHash(context.Background(), tx.Hash()) {
if err != nil {
return nil, fmt.Errorf("unable to get transaction by hash: %v", err)
}
time.Sleep(1 * time.Second)
}
log.Info(fmt.Sprintf("New contract deployed at %s", addr.String()))
return contract, nil
}
contract, err := contracts.NewSMC(sharding.ShardingManagerAddress, c.client)
if err != nil {
}
return contract, nil
}

View File

@@ -1,34 +1,26 @@
package sharding
package collator
import (
"context"
"fmt"
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/sharding/contracts"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/client"
)
type collator interface {
Account() *accounts.Account
ChainReader() ethereum.ChainReader
SMCCaller() *contracts.SMCCaller
}
// SubscribeBlockHeaders checks incoming block headers and determines if
// we are an eligible collator 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
func subscribeBlockHeaders(c collator) error {
func subscribeBlockHeaders(c client.Client) error {
headerChan := make(chan *types.Header, 16)
account := c.Account()
_, err := c.ChainReader().SubscribeNewHead(context.Background(), headerChan)
if err != nil {
return fmt.Errorf("unable to subscribe to incoming headers. %v", err)
@@ -53,7 +45,6 @@ func subscribeBlockHeaders(c collator) error {
return fmt.Errorf("unable to watch shards. %v", err)
}
} else {
log.Warn(fmt.Sprintf("Account %s not in collator pool.", account.Address.String()))
}
}
@@ -63,12 +54,10 @@ func subscribeBlockHeaders(c collator) error {
// collation for the available shards in the SMC. The function calls
// getEligibleCollator from the SMC and collator a collation if
// conditions are met
func checkSMCForCollator(c collator, head *types.Header) error {
account := c.Account()
func checkSMCForCollator(c client.Client, head *types.Header) error {
log.Info("Checking if we are an eligible collation collator for a shard...")
period := big.NewInt(0).Div(head.Number, big.NewInt(periodLength))
for s := int64(0); s < shardCount; s++ {
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)
@@ -77,7 +66,7 @@ func checkSMCForCollator(c collator, head *types.Header) error {
}
// If output is non-empty and the addr == coinbase
if addr == account.Address {
if addr == c.Account().Address {
log.Info(fmt.Sprintf("Selected as collator on shard: %d", s))
err := submitCollation(s)
if err != nil {
@@ -93,10 +82,14 @@ func checkSMCForCollator(c collator, head *types.Header) error {
// 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 collator) (bool, error) {
func isAccountInCollatorPool(c client.Client) (bool, error) {
account := c.Account()
// Checks if our deposit has gone through according to the SMC
return c.SMCCaller().IsCollatorDeposited(&bind.CallOpts{}, account.Address)
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()))
}
return b, err
}
// submitCollation interacts with the SMC directly to add a collation header
@@ -128,3 +121,22 @@ func submitCollation(shardID int64) error {
log.Info("Submit collation function called")
return nil
}
// joinCollatorPool checks if the account is a collator in the SMC. If
// the account is not in the set, it will deposit 100ETH into contract.
func joinCollatorPool(c client.Client) error {
log.Info("Joining collator pool")
txOps, err := c.CreateTXOps(sharding.CollatorDeposit)
if err != nil {
return fmt.Errorf("unable to intiate the deposit transaction: %v", err)
}
tx, err := c.SMCTransactor().Deposit(txOps)
if err != nil {
return fmt.Errorf("unable to deposit eth and become a collator: %v", err)
}
log.Info(fmt.Sprintf("Deposited %dETH into contract with transaction hash: %s", new(big.Int).Div(sharding.CollatorDeposit, big.NewInt(params.Ether)), tx.Hash().String()))
return nil
}

View File

@@ -0,0 +1,41 @@
// 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)
}

View File

@@ -0,0 +1,91 @@
package collator
import (
"math/big"
"testing"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/contracts"
)
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
accountBalance1001Eth, _ = new(big.Int).SetString("1001000000000000000000", 10)
)
// Mock client for testing collator. Should this go into sharding/client/testing?
type mockClient struct {
smc *contracts.SMC
t *testing.T
}
func (m *mockClient) Account() *accounts.Account {
return &accounts.Account{Address: addr}
}
func (m *mockClient) SMCCaller() *contracts.SMCCaller {
return &m.smc.SMCCaller
}
func (m *mockClient) ChainReader() ethereum.ChainReader {
m.t.Fatal("ChainReader not implemented")
return nil
}
func (m *mockClient) SMCTransactor() *contracts.SMCTransactor {
return &m.smc.SMCTransactor
}
func (m *mockClient) CreateTXOps(value *big.Int) (*bind.TransactOpts, error) {
m.t.Fatal("CreateTXOps not implemented")
return nil, nil
}
// Unused mockClient methods
func (m *mockClient) Start() error {
m.t.Fatal("Start called")
return nil
}
func (m *mockClient) Close() {
m.t.Fatal("Close called")
}
func TestIsAccountInCollatorPool(t *testing.T) {
// Test setup (should this go to sharding/client/testing?)
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
transactOpts := bind.NewKeyedTransactor(key)
_, _, smc, _ := contracts.DeploySMC(transactOpts, backend)
backend.Commit()
client := &mockClient{smc: smc, t: t}
// address should not be in pool initially
b, err := isAccountInCollatorPool(client)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("Account unexpectedly in collator pool")
}
// deposit in collator pool, then it should return true
transactOpts.Value = sharding.CollatorDeposit
if _, err := smc.Deposit(transactOpts); err != nil {
t.Fatalf("Failed to deposit: %v", err)
}
backend.Commit()
b, err = isAccountInCollatorPool(client)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("Account not in collator pool when expected to be")
}
}

View File

@@ -1,125 +0,0 @@
package sharding
import (
"context"
"flag"
"math/big"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/sharding/contracts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
cli "gopkg.in/urfave/cli.v1"
)
// FakeEthService based on implementation of internal/ethapi.Client
type FakeEthService struct{}
// eth_getCode
func (s *FakeEthService) GetCode(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (string, error) {
return contracts.SMCBin, nil
}
func (s *FakeEthService) GasPrice(ctx context.Context) (hexutil.Big, error) {
b := big.NewInt(1000)
return hexutil.Big(*b), nil
}
func (s *FakeEthService) EstimateGas(ctx context.Context, msg interface{}) (hexutil.Uint64, error) {
h := hexutil.Uint64(uint64(1000000))
return h, nil
}
func (s *FakeEthService) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (hexutil.Uint64, error) {
return hexutil.Uint64(uint64(1)), nil
}
func (s *FakeEthService) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
return common.Hash{}, nil
}
func (s *FakeEthService) GetTransactionReceipt(hash common.Hash) (*types.Receipt, error) {
return &types.Receipt{
ContractAddress: common.StringToAddress("0x1"),
Logs: []*types.Log{},
}, nil
}
func (s *FakeEthService) GetTransactionByHash(hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
return nil, false, nil
}
type FakeNetworkService struct{}
func (s *FakeNetworkService) Version() (string, error) {
return "100", nil
}
type FakeNewHeadsService struct{}
func (s *FakeNewHeadsService) NewHeads() {
}
// TODO: Use a simulated backend rather than starting a fake node.
func newTestServer(endpoint string) (*rpc.Server, error) {
// Create a default account without password.
scryptN, scryptP, keydir, err := (&node.Config{DataDir: endpoint}).AccountConfig()
if err != nil {
return nil, err
}
if _, err := keystore.StoreKey(keydir, "" /*password*/, scryptN, scryptP); err != nil {
return nil, err
}
// Create server and register eth service with FakeEthService
server := rpc.NewServer()
if err := server.RegisterName("eth", new(FakeEthService)); err != nil {
return nil, err
}
if err := server.RegisterName("net", new(FakeNetworkService)); err != nil {
return nil, err
}
if err := server.RegisterName("newHeads", new(FakeNewHeadsService)); err != nil {
return nil, err
}
l, err := rpc.CreateIPCListener(endpoint + "/geth.ipc")
if err != nil {
return nil, err
}
go server.ServeListener(l)
return server, nil
}
func createContext() *cli.Context {
set := flag.NewFlagSet("test", 0)
set.String(utils.DataDirFlag.Name, "", "")
return cli.NewContext(nil, set, nil)
}
// TODO(prestonvanloon): Fix this test.
// func TestShardingClient(t *testing.T) {
// endpoint := path.Join(os.TempDir(), fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()))
// server, err := newTestServer(endpoint)
// if err != nil {
// t.Fatalf("Failed to create a test server: %v", err)
// }
// defer server.Stop()
// ctx := createContext()
// if err := ctx.GlobalSet(utils.DataDirFlag.Name, endpoint); err != nil {
// t.Fatalf("Failed to set global variable for flag %s. Error: %v", utils.DataDirFlag.Name, err)
// }
// c := MakeCollatorClient(ctx)
// if err := c.Start(); err != nil {
// t.Errorf("Failed to start server: %v", err)
// }
// }

View File

@@ -1,119 +0,0 @@
package sharding
import (
"context"
"errors"
"math/big"
"strings"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/sharding/contracts"
"github.com/ethereum/go-ethereum/core/types"
)
type FakeCollatorClient struct {
accountAccount *accounts.Account
accountError error
chainReader FakeChainReader
contractCaller FakeContractCaller
}
func (c FakeCollatorClient) Account() *accounts.Account {
return c.accountAccount
}
func (c FakeCollatorClient) ChainReader() ethereum.ChainReader {
return c.chainReader
}
func (c FakeCollatorClient) SMCCaller() *contracts.SMCCaller {
SMCCaller, err := contracts.NewSMCCaller(common.HexToAddress("0x0"), c.contractCaller)
if err != nil {
panic(err)
}
return SMCCaller
}
type FakeChainReader struct {
subscribeNewHeadSubscription ethereum.Subscription
subscribeNewHeadError error
}
func (r FakeChainReader) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
return r.subscribeNewHeadSubscription, r.subscribeNewHeadError
}
func (r FakeChainReader) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return nil, nil
}
func (r FakeChainReader) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
return nil, nil
}
func (r FakeChainReader) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return nil, nil
}
func (r FakeChainReader) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return nil, nil
}
func (r FakeChainReader) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
return 0, nil
}
func (r FakeChainReader) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
return nil, nil
}
type FakeContractCaller struct {
codeAtBytes []byte
codeAtError error
callContractBytes []byte
callContractError error
}
func (c FakeContractCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
return c.codeAtBytes, c.codeAtError
}
func (c FakeContractCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return c.callContractBytes, c.callContractError
}
func TestCheckSMCForCollator(t *testing.T) {
tests := []struct {
Name string
Head *types.Header
ExpectedPeriod *big.Int
ExpectedError string
CollatorClient FakeCollatorClient
}{
{
Name: "SMCCaller.checkSMCForCollator should return an error",
ExpectedError: "there is no cake",
CollatorClient: FakeCollatorClient{
accountAccount: &accounts.Account{},
contractCaller: FakeContractCaller{
callContractError: errors.New("there is no cake"),
},
},
Head: &types.Header{Number: big.NewInt(100)},
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := checkSMCForCollator(tt.CollatorClient, tt.Head); !strings.Contains(safeError(err), tt.ExpectedError) {
t.Fatalf("Incorrect error! Wanted %v, got %v", tt.ExpectedError, err)
}
})
}
}
func safeError(err error) string {
if err != nil {
return err.Error()
}
return "nil"
}

View File

@@ -6,19 +6,33 @@ import (
"github.com/ethereum/go-ethereum/common"
)
//go:generate abigen --sol contracts/sharding_manager.sol --pkg contracts --out contracts/sharding_manager.go
var (
// Number of network shards
shardCount = int64(1)
ShardCount = int64(1)
// Address of the sharding manager contract
shardingManagerAddress = common.HexToAddress("0x0") // TODO
ShardingManagerAddress = common.HexToAddress("0x0") // TODO
// Gas limit for verifying signatures
sigGasLimit = 40000
SigGasLimit = 40000
// Number of blocks in a period
periodLength = int64(5)
PeriodLength = int64(5)
// Number of periods ahead of current period which the contract is able to return the collator of that period.
lookaheadPeriods = 4
// Required deposit size in wei
depositSize = new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil) // 100 ETH
LookaheadPeriods = 4
// Required deposit size in wei for collator
CollatorDeposit = new(big.Int).Exp(big.NewInt(10), big.NewInt(21), nil) // 1000 ETH
// Required deposit size in wei for proposer
ProposerDeposit = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) // 1 ETH
// Minimum Balance of proposer where bids are decuted
MinProposerBalance = new(big.Int).Exp(big.NewInt(10), big.NewInt(17), nil) // 0.1 ETH
// Gas limit to create contract
contractGasLimit = uint64(4700000) // Max is 4712388
ContractGasLimit = uint64(4700000) // Max is 4712388
// Number of collations collators need to check to apply fork choice rule
WindbackLength = int64(25)
// Number of periods to lockup collator deposit from time of deregistration
CollatorLockupLength = int64(16128)
// Number of periods to lockup proposer deposit from time of deregistration
ProposerLockupLength = int64(48)
// Number of vETH awarded to collators after collation included in canonical chain
CollatorSubsidy = new(big.Int).Exp(big.NewInt(10), big.NewInt(15), nil) // 0.001 ETH
)

View File

@@ -5,12 +5,42 @@ import (
"testing"
)
func TestDepositSize(t *testing.T) {
want, err := new(big.Int).SetString("100000000000000000000", 10) // 100 ETH
func TestCollatorDeposit(t *testing.T) {
want, err := new(big.Int).SetString("1000000000000000000000", 10) // 1000 ETH
if !err {
t.Fatalf("Failed to setup test")
}
if depositSize.Cmp(want) != 0 {
t.Errorf("depositSize incorrect. Wanted %d, got %d", want, depositSize)
if CollatorDeposit.Cmp(want) != 0 {
t.Errorf("Collator deposit size incorrect. Wanted %d, got %d", want, CollatorDeposit)
}
}
func TestProposerDeposit(t *testing.T) {
want, err := new(big.Int).SetString("1000000000000000000", 10) // 1 ETH
if !err {
t.Fatalf("Failed to setup test")
}
if ProposerDeposit.Cmp(want) != 0 {
t.Errorf("Proposer deposit size incorrect. Wanted %d, got %d", want, ProposerDeposit)
}
}
func TestMinProposerBalance(t *testing.T) {
want, err := new(big.Int).SetString("100000000000000000", 10) // 0.1 ETH
if !err {
t.Fatalf("Failed to setup test")
}
if MinProposerBalance.Cmp(want) != 0 {
t.Errorf("Min proposer balance incorrect. Wanted %d, got %d", want, MinProposerBalance)
}
}
func TestCollatorSubsidy(t *testing.T) {
want, err := new(big.Int).SetString("1000000000000000", 10) // 0.001 ETH
if !err {
t.Fatalf("Failed to setup test")
}
if CollatorSubsidy.Cmp(want) != 0 {
t.Errorf("Collator subsidy size incorrect. Wanted %d, got %d", want, CollatorSubsidy)
}
}

View File

@@ -0,0 +1,5 @@
// Package contracts consists of generated contracts for sharding.
package contracts
// DO NOT USE THIS FILE: It exists only for the package comment. Other files in this package are
// automatically generated and cannot have a package comment.

File diff suppressed because one or more lines are too long

View File

@@ -15,8 +15,8 @@ import (
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
accountBalance1000Eth, _ = new(big.Int).SetString("1000000000000000000000", 10)
collatorDeposit, _ = new(big.Int).SetString("100000000000000000000", 10)
accountBalance1001Eth, _ = new(big.Int).SetString("1001000000000000000000", 10)
collatorDeposit, _ = new(big.Int).SetString("1000000000000000000000", 10)
)
func deploySMCContract(backend *backends.SimulatedBackend) (common.Address, *types.Transaction, *SMC, error) {
@@ -27,7 +27,7 @@ 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: accountBalance1000Eth}})
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
_, _, _, err := deploySMCContract(backend)
backend.Commit()
if err != nil {
@@ -37,7 +37,7 @@ func TestContractCreation(t *testing.T) {
// Test getting the collation gas limit
func TestGetCollationGasLimit(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1000Eth}})
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
_, _, smc, _ := deploySMCContract(backend)
gasLimit, err := smc.GetCollationGasLimit(&bind.CallOpts{})
if err != nil {
@@ -50,7 +50,7 @@ func TestGetCollationGasLimit(t *testing.T) {
// Test collator deposit
func TestCollatorDeposit(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1000Eth}})
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
transactOpts := bind.NewKeyedTransactor(key)
_, _, smc, _ := deploySMCContract(backend)
@@ -102,7 +102,7 @@ func TestCollatorDeposit(t *testing.T) {
// Test collator withdraw
func TestCollatorWithdraw(t *testing.T) {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1000Eth}})
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
transactOpts := bind.NewKeyedTransactor(key)
_, _, smc, _ := deploySMCContract(backend)

View File

@@ -0,0 +1,36 @@
// Package proposer holds all of the functionality to run as a collation proposer in a sharded
// system.
package proposer
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/sharding/client"
cli "gopkg.in/urfave/cli.v1"
)
type Proposer interface {
Start() error
}
type proposer struct {
client client.Client
}
func NewProposer(ctx *cli.Context) Proposer {
return &proposer{
client: client.NewClient(ctx),
}
}
func (p *proposer) Start() error {
log.Info("Starting proposer client")
err := p.client.Start()
if err != nil {
return err
}
defer p.client.Close()
// TODO: Propose collations
return nil
}

View File

@@ -1,79 +0,0 @@
package sharding
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sharding/contracts"
)
// initSMC initializes the sharding manager contract bindings.
// If the SMC does not exist, it will be deployed.
func initSMC(c *collatorClient) error {
b, err := c.client.CodeAt(context.Background(), shardingManagerAddress, nil)
if err != nil {
return fmt.Errorf("unable to get contract code at %s: %v", shardingManagerAddress, err)
}
if len(b) == 0 {
log.Info(fmt.Sprintf("No sharding manager contract found at %s. Deploying new contract.", shardingManagerAddress.String()))
txOps, err := c.createTXOps(big.NewInt(0))
if err != nil {
return fmt.Errorf("unable to intiate the transaction: %v", err)
}
addr, tx, contract, err := contracts.DeploySMC(txOps, c.client)
if err != nil {
return fmt.Errorf("unable to deploy sharding manager contract: %v", err)
}
for pending := true; pending; _, pending, err = c.client.TransactionByHash(context.Background(), tx.Hash()) {
if err != nil {
return fmt.Errorf("unable to get transaction by hash: %v", err)
}
time.Sleep(1 * time.Second)
}
c.smc = contract
log.Info(fmt.Sprintf("New contract deployed at %s", addr.String()))
} else {
contract, err := contracts.NewSMC(shardingManagerAddress, c.client)
if err != nil {
return fmt.Errorf("failed to create sharding contract: %v", err)
}
c.smc = contract
}
return nil
}
// joinCollatorPool checks if the account is a collator in the SMC. If
// the account is not in the set, it will deposit 100ETH into contract.
func joinCollatorPool(c *collatorClient) error {
if c.ctx.GlobalBool(utils.DepositFlag.Name) {
log.Info("Joining collator pool")
txOps, err := c.createTXOps(depositSize)
if err != nil {
return fmt.Errorf("unable to intiate the deposit transaction: %v", err)
}
tx, err := c.smc.SMCTransactor.Deposit(txOps)
if err != nil {
return fmt.Errorf("unable to deposit eth and become a collator: %v", err)
}
log.Info(fmt.Sprintf("Deposited %dETH into contract with transaction hash: %s", new(big.Int).Div(depositSize, big.NewInt(params.Ether)), tx.Hash().String()))
} else {
log.Info("Not joining collator pool")
}
return nil
}