Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Ameen Soleimani committed May 29, 2017
1 parent 922bcb4 commit 431ff2b
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 27 deletions.
173 changes: 157 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

## Tests

At the moment, only the contract tests have been written.
At the moment, only the contract tests have been written. To run the tests:

To run tests:
First start testrpc in one terminal.

(in a separate terminal)
```bash
npm run testrpc
```

Then run the tests in a different terminal.

```bash
npm run mocha
```
Expand All @@ -26,7 +27,7 @@ For traditional ad contracts with 30 to 60 day settlement cycles, discrepancies
By syncronizing impressions in real-time, discrepancies can be eliminated or at
least discovered much more quickly.

### AdChain Overview
### AdChain

This repo is being developed as part of the AdChain project, a collaboration
between ConsenSys and MetaX.
Expand Down Expand Up @@ -62,39 +63,179 @@ as starting points.

The advertiser or their agent (demand) and the publisher or their agent (supply)
will maintain a state channel for the duration of their business relationship,
periodically checkpointing the channel state onchain.
periodically checkpointing the channel state onchain. All data can be kept private between the parties, even during checkpointing, unless there is a dispute.

This state channel tracks the impressions between demand and supply and can be thought of as an immutable "impression ledger". In response to browser ad impression events, the demand will send a signed state channel update over HTTP to the supply, acknowledging the impression. Both supply and demand store these channel updates offchain, in traditional databases such as PostgreSQL or MongoDB.

This state channel tracks the impressions between represent an "impression ledger"
The AdMarket operator plays a role as a passive observer and a tie-breaker in the event the supply witnesses an impression event the demand fails to acknowledge.

#### Registration

In the current implementation, both the demand and supply must be registered
with the AdMarket in order to open channels.
Both the demand and supply must be registered with the AdMarket in order to open channels. The AdMarket contract maintains a mapping of Ethereum addresses to url strings for registered members. The url strings point to adservers which will handle state channel messages.

```
mapping (address => string) public registeredDemand;
mapping (address => string) public registeredSupply;
```

Only the owner of the AdMarket contract may register (or deregister) supply and demand
participants.

To register demand or supply, the AdMarket owner must provide their Ethereum address as well as a url string which points to their adserver which will handle state channel messages.
To register demand or supply, the AdMarket owner must provide their Ethereum address as well as a url string which points to their adserver which will handle offchain HTTP state channel messages.

```
function registerDemand(address demand, string url) only_owner {...}
function registerSupply(address supply, string url) only_owner {...}
function deregisterDemand(address demand) only_owner {...}
function deregisterSupply(address supply) only_owner {...}
```

Once registration is complete, the supply and demand may update their own
adserver urls.

##### Integration with the AdChain Registry
```
function updateDemandUrl(string url) only_registered_demand {...}
function updateSupplyUrl(string url) only_registered_supply {...}
```

##### Future Integration with the AdChain Registry

In the future, registration will be separated into its own contract so an AdMarket contract can opt to instead use the AdChain Registry as its canonical source of truth regarding supply and demand identity.
In the future, registration functionality will be removed in favor of interfacing directly with the AdChain Registry.

#### Opening the Channel

The demand opens the channel initially,
In this system, only the demand can open the channel, which it does by providing the address of a registered supply which it doesn't already have an open channel with.

#### Checkpointing On Chain
```
function openChannel(address supply) only_registered_demand {...}
```

#### Closing the Channel
At the moment, the channel is only used for accounting purposes and payments are done out-of-band, so opening a channel does not require a monetary deposit.

#### State Updates

Once a channel is open, whenever the demand receives an impression event from a user's browser, it will generate a state channel update acknowledging the impression, save it, sign it, and send it to the supply.

The **signed** portions of state channel message include the following fields:

- contractId - the Ethereum address of the AdMarket contract
- channelId - the integer id for this channel
- demand - the Ethereum address of the demand
- supply - the Ethereum address of the supply
- root - the merkle root of the most recent channel state

Where the channel state includes the following fields:

- balance - the cumulative amount demand owes supply
- impressions - the cumulative number of impressions
- impressionId - the id of the latest impression
- impressionPrice - the price of the latest impression
- prevRoot - the merkle root of the previous channel state

Because the channel state includes the previous root, the **root** of each channel state acts as a unique identifier not only for that specific state, but for the entire historical record of states leading up to it.

Upon receiving the state channel message, the supply will verify it and save it to persistant storage.


#### Channel Data

The channel data in the AdMarket contract is set once when the channel is opened and periodically as the channel is checkpointed. Only the `root` is actually updated; the metadata serves to guide the channel through the proper checkpointing flow.

```
struct Channel {
// State Variables (only root changes on each channel update)
address contractId;
bytes32 channelId;
address demand;
address supply;
bytes32 root;
// Metadata (not included in offchain state updates)
ChannelState state;
uint256 expiration;
uint256 challengeTimeout;
bytes32 proposedRoot;
}
enum ChannelState { Open, Checkpointing, Closing, Closed }
```

- `expiration` - block number after which the channel expires and can be closed by anyone (set in `openChannel`)
- `challengeTimeout` - block number after which a proposed checkpoint or valid challenge can be finalized (set in `proposeCheckpointChannel` and `challengeCheckpointChannel`)
- `proposedRoot` - a placeholder root which is only stored and set after the challenge period is over

#### Checkpointing the Channel

#### Disputes
Periodically, the demand or supply can checkpoint the channel on the AdMarket contract, and optionally renew the channel.

##### Propose Checkpoint

Checkpointing the channel happens in a few steps. The first step is to propose a checkpoint for the most recent signed state, indicated by its root. As mentioned above, the root acts as a unique fingerprint for the entire historical record of impressions for this channel, and checkpointing it amounts to a globally visible commitment to that record.

### Architecture
The signature provided must be from the demand and is verified in the contract method.

If `renew` is set to `true`, the channel will remain open once the checkpoint is completed.

```
function proposeCheckpointChannel(
bytes32 channelId,
bytes32 proposedRoot,
bytes signature,
bool renew
) {...}
```

##### Challenge Checkpoint

Proposing a checkpoint triggers a challenge period during which either party can challenge the `proposedRoot` by submitting a signed state update -- the `challengeRoot` -- with a higher impression count (the impressions count is sequence number). The index of the impressions in the state array and the corresponding merkle proof are required to verify that the impressions value is included in the `challengeRoot`.

```
function challengeCheckpointChannel(
bytes32 channelId,
bytes32 challengeRoot,
uint256 impressions,
uint256 index,
bytes merkleProof,
bytes signature
) {...}
```

##### Accept Challenge

If a challenge was issued, the challenge period is reset, providing time to answer the challenge. To accept the challenge, the party must provide an impressions count which can be proven to be included in the original `proposedRoot`, and is higher than the impressions in the challenge. Should this be the case, the checkpointing immediately completes and the original `proposedRoot` is recorded.

```
function acceptChallengeCheckpointChannel(
bytes32 channelId,
uint256 impressions,
uint256 index,
bytes merkleProof
) {...}
```

##### Checkpoint

If there is no valid challenge, then after the challenge period expires either party can finalize the proposed checkpoint, and `root` will be set to `proposedRoot`.

Alternatively, if there is a valid challenge which is not accepted within the (reset) challenge period, then calling the `checkpointChannel` method will set `root` to `challengeRoot`.

```
function checkpointChannel(bytes32 channelId) {...}:w
```



#### Closing the Channel

Closing a channel uses the same methods and follows the same flow as checkpointing, except with `renew` set to `false` from the beginning.

If checkpointing was initiated with `renew` set to `true`, either party can still decide to close the channel using the `closeChannel` method. This can be done at any time during the checkpointing process, and sets `renew` to `false`.

```
function closeChannel(bytes32 channelId) {...}
```

## License

MIT
20 changes: 9 additions & 11 deletions contracts/AdMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,7 @@ contract AdMarket is ECVerify {
// at any point during the checkpointing process.
// The checkpointing process would continue in the same exact way, but it would
// close upon completion instead of remaining open
function closeChannel(
bytes32 channelId
) {
function closeChannel(bytes32 channelId) {
Channel channel = channels[channelId];

// Check that msg.sender is either demand or supply
Expand All @@ -242,14 +240,14 @@ contract AdMarket is ECVerify {
// They also supply the proof for this impression count and the Demand's signature on it.
// This resets the challengeTimeout giving the counterparty a chance to accept this challenge.
// To accept the challenge, the counterparty must prove that the original checkpointed root has more impressions
function challengeCheckpointChannel(
bytes32 channelId,
bytes32 challengeRoot,
uint256 impressions,
uint256 index,
bytes merkleProof,
bytes signature
) {
function challengeCheckpointChannel(
bytes32 channelId,
bytes32 challengeRoot,
uint256 impressions,
uint256 index,
bytes merkleProof,
bytes signature
) {
Channel channel = channels[channelId];

// Check that msg.sender is either demand or supply
Expand Down

0 comments on commit 431ff2b

Please sign in to comment.