Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative crosslink data construction, take 1 #1278

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions scripts/build_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@
)

from eth2spec.utils.ssz.ssz_impl import (
zpad,
hash_tree_root,
is_empty,
signing_root,
serialize,
is_empty,
)
from eth2spec.utils.ssz.ssz_typing import (
bit, boolean, Container, List, Vector, Bytes, uint64,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, BytesN, Bitlist, Bitvector,
)
from eth2spec.utils.bls import (
bls_aggregate_pubkeys,
Expand Down
62 changes: 33 additions & 29 deletions specs/core/1_shard-data-chains.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- [Misc](#misc)
- [Initial values](#initial-values)
- [Time parameters](#time-parameters)
- [Signature domains](#signature-domains)
- [Signature domain types](#signature-domain-types)
- [TODO PLACEHOLDER](#todo-placeholder)
- [Data structures](#data-structures)
- [`ShardBlockBody`](#shardblockbody)
Expand All @@ -27,6 +27,8 @@
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [`get_shard_header`](#get_shard_header)
- [`verify_shard_attestation_signature`](#verify_shard_attestation_signature)
- [`flatten_block`](#flatten_block)
- [`compute_crosslink_data`](#compute_crosslink_data)
- [`compute_crosslink_data_root`](#compute_crosslink_data_root)
- [Object validity](#object-validity)
- [Shard blocks](#shard-blocks)
Expand All @@ -46,6 +48,7 @@ This document describes the shard data layer and the shard fork choice rule in P

| Name | Value |
| - | - |
| `BYTES_PER_SHARD_BLOCK_HEADER` | `2**9` (= 512) |
| `BYTES_PER_SHARD_BLOCK_BODY` | `2**14` (= 16,384) |
| `MAX_SHARD_ATTESTIONS` | `2**4` (= 16) |

Expand Down Expand Up @@ -76,7 +79,7 @@ The following types are defined, mapping into `DomainType` (little endian):

| Name | Value |
| - | - |
| `PLACEHOLDER` | `2**32` |
| `PLACEHOLDER` | `2**3` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to 2**3 for testing.


## Data structures

Expand Down Expand Up @@ -107,7 +110,7 @@ class ShardBlock(Container):
shard: Shard
beacon_chain_root: Bytes32
parent_root: Bytes32
data: ShardBlockBody
body: ShardBlockBody
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also hotfix in #1298.

state_root: Bytes32
attestations: List[ShardAttestation, PLACEHOLDER]
signature: BLSSignature
Expand Down Expand Up @@ -247,36 +250,37 @@ def verify_shard_attestation_signature(state: BeaconState,
)
```

### `flatten_block`

```python
def flatten_block(block: ShardBlock) -> Bytes:
vbuterin marked this conversation as resolved.
Show resolved Hide resolved
return zpad(serialize(get_shard_header(block)), BYTES_PER_SHARD_BLOCK_HEADER) + serialize(block.body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added serialize() here, correct?

```

### `compute_crosslink_data`

```python
def compute_crosslink_data(blocks: Sequence[ShardBlock]) -> bytes:
"""
Flattens a series of blocks. Designed to be equivalent to SSZ serializing a list of
flattened blocks with one empty block at the end as a termination marker.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate what do you mean by "one empty block at the end" here?

Copy link
Contributor

@hwwhww hwwhww Jul 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate what's the main advantage of this one over just using SSZ serialize like:

class CrosslinkingBlocks:
    data: List[ShardBlock, SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK]

assert len(blocks) <= SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
crosslinking_blocks = CrosslinkingBlocks(data=blocks)
return serialize(crosslinking_blocks)

?

"""
positions = [4 * len(blocks)]
bodies = []
for block in blocks:
bodies.append(flatten_block(block))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't flattening the block here reduce our ability to construct proofs about components of a block through the crosslink root? Still possible but not as clean due to flattening to bytes

positions.append(positions[-1] + len(bodies[-1]))
return b''.join([int_to_bytes(pos, 4) for pos in positions]) + b''.join(bodies)
```

### `compute_crosslink_data_root`

```python
def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Bytes32:
def is_power_of_two(value: uint64) -> bool:
return (value > 0) and (value & (value - 1) == 0)

def pad_to_power_of_2(values: MutableSequence[bytes]) -> Sequence[bytes]:
while not is_power_of_two(len(values)):
values.append(b'\x00' * BYTES_PER_SHARD_BLOCK_BODY)
return values

def hash_tree_root_of_bytes(data: bytes) -> bytes:
return hash_tree_root([data[i:i + 32] for i in range(0, len(data), 32)])

def zpad(data: bytes, length: uint64) -> bytes:
return data + b'\x00' * (length - len(data))

return hash(
# TODO untested code.
# Need to either pass a typed list to hash-tree-root, or merkleize_chunks(values, pad_to=2**x)
hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(
zpad(serialize(get_shard_header(block)), BYTES_PER_SHARD_BLOCK_BODY)
) for block in blocks
]))
+ hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(block.body) for block in blocks
]))
)
MAXLEN = (
BYTES_PER_SHARD_BLOCK_HEADER + BYTES_PER_SHARD_BLOCK_BODY
) * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHARD_SLOTS_PER_EPOCH is undefined. Should it be SHARD_SLOTS_PER_BEACON_SLOT(#1276) * SLOTS_PER_EPOCH?

return hash_tree_root(BytesN[MAXLEN](zpad(compute_crosslink_data(blocks), MAXLEN)))
```

## Object validity
Expand Down
28 changes: 17 additions & 11 deletions test_libs/pyspec/eth2spec/utils/ssz/ssz_impl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from ..merkle_minimal import merkleize_chunks
from ..merkle_minimal import (
merkleize_chunks,
)
from ..hash_function import hash
from .ssz_typing import (
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bits, boolean, Container, List, Bytes,
Expand All @@ -8,6 +10,7 @@
# SSZ Serialization
# -----------------------------

BYTES_PER_CHUNK = 32
BYTES_PER_LENGTH_OFFSET = 4


Expand Down Expand Up @@ -101,14 +104,17 @@ def pack(values: Series):
return b''.join([serialize_basic(value) for value in values])


def chunkify(bytez):
# pad `bytez` to nearest 32-byte multiple
bytez += b'\x00' * (-len(bytez) % 32)
return [bytez[i:i + 32] for i in range(0, len(bytez), 32)]
def zpad(bytez, bytes_per_chunk):
return bytez + b'\x00' * (-len(bytez) % bytes_per_chunk)

def chunkify(bytez, bytes_per_chunk):
# pad `bytez` to nearest `bytes_per_chunk`-byte multiple
bytez = zpad(bytez, bytes_per_chunk)
return [bytez[i:i + bytes_per_chunk] for i in range(0, len(bytez), bytes_per_chunk)]


def mix_in_length(root, length):
return hash(root + length.to_bytes(32, 'little'))
return hash(root + length.to_bytes(BYTES_PER_CHUNK, 'little'))


def is_bottom_layer_kind(typ: SSZType):
Expand All @@ -122,7 +128,7 @@ def item_length(typ: SSZType) -> int:
if issubclass(typ, BasicValue):
return typ.byte_len
else:
return 32
return BYTES_PER_CHUNK


def chunk_count(typ: SSZType) -> int:
Expand All @@ -131,7 +137,7 @@ def chunk_count(typ: SSZType) -> int:
elif issubclass(typ, Bits):
return (typ.length + 255) // 256
elif issubclass(typ, Elements):
return (typ.length * item_length(typ.elem_type) + 31) // 32
return (typ.length * item_length(typ.elem_type) + BYTES_PER_CHUNK - 1) // BYTES_PER_CHUNK
elif issubclass(typ, Container):
return len(typ.get_fields())
else:
Expand All @@ -141,11 +147,11 @@ def chunk_count(typ: SSZType) -> int:
def hash_tree_root(obj: SSZValue):
if isinstance(obj, Series):
if is_bottom_layer_kind(obj.type()):
leaves = chunkify(pack(obj))
leaves = chunkify(pack(obj), BYTES_PER_CHUNK)
else:
leaves = [hash_tree_root(value) for value in obj]
elif isinstance(obj, BasicValue):
leaves = chunkify(serialize_basic(obj))
leaves = chunkify(serialize_basic(obj), BYTES_PER_CHUNK)
else:
raise Exception(f"Type not supported: {type(obj)}")

Expand All @@ -159,4 +165,4 @@ def signing_root(obj: Container):
# ignore last field
fields = [field for field in obj][:-1]
leaves = [hash_tree_root(f) for f in fields]
return merkleize_chunks(chunkify(b''.join(leaves)))
return merkleize_chunks(chunkify(b''.join(leaves), BYTES_PER_CHUNK))