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

DSMR package #1712

Merged
merged 98 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
bc500b2
Start DSMR
aaronbuchwald Sep 19, 2024
605dfd0
WIP
aaronbuchwald Sep 23, 2024
1c0b759
Update README
aaronbuchwald Oct 1, 2024
e598667
Block -> ExecutionBlock
joshua-kim Oct 2, 2024
203ac52
StatelessBlock -> Block
joshua-kim Oct 2, 2024
01fa319
remove snowman.Block functions from Block
joshua-kim Oct 2, 2024
66e19a2
export Block.id
joshua-kim Oct 2, 2024
9283eac
add tx interface
joshua-kim Oct 2, 2024
09efcf7
rename LocalChunkMempool -> Mempool
joshua-kim Oct 2, 2024
d725d80
update mempool interface
joshua-kim Oct 2, 2024
59fe2d7
refactor mempool
joshua-kim Oct 2, 2024
7ef316d
rename Backend -> VM
joshua-kim Oct 2, 2024
b69dfcd
nti
joshua-kim Oct 2, 2024
cc04606
remove consts
joshua-kim Oct 2, 2024
1a01b7f
nit
joshua-kim Oct 2, 2024
40d3597
nit
joshua-kim Oct 2, 2024
f03a376
nit
joshua-kim Oct 2, 2024
e0d216b
nit
joshua-kim Oct 2, 2024
cb85931
nit
joshua-kim Oct 3, 2024
4cd4e70
nit
joshua-kim Oct 3, 2024
c217375
nit
joshua-kim Oct 3, 2024
4f604c3
DSMR storage (#1638)
aaronbuchwald Oct 8, 2024
30db8b3
Update DSMR README (#1639)
aaronbuchwald Oct 8, 2024
7e1cab4
nit
joshua-kim Oct 8, 2024
bfa3228
Update chunk block executor (#1646)
aaronbuchwald Oct 9, 2024
6e460b7
nit
joshua-kim Oct 9, 2024
dcc1292
wip
joshua-kim Oct 10, 2024
85933b2
nit
joshua-kim Oct 10, 2024
618375d
fix ut
joshua-kim Oct 10, 2024
a28960b
wip
joshua-kim Oct 10, 2024
864f03d
Squashed commit of the following:
joshua-kim Oct 10, 2024
2018a0d
nit
joshua-kim Oct 10, 2024
3e00658
nit
joshua-kim Oct 10, 2024
68b7d2b
DSMR Warp Certificates (#1663)
aaronbuchwald Oct 14, 2024
2aa6666
nit
joshua-kim Oct 14, 2024
3dd0c74
nit
joshua-kim Oct 16, 2024
8e96394
nit
joshua-kim Oct 17, 2024
48585f1
nit
joshua-kim Oct 17, 2024
5fab6bc
ut
joshua-kim Oct 17, 2024
c616db7
cleanup todo
joshua-kim Oct 17, 2024
4e9d493
nit
joshua-kim Oct 17, 2024
198afb9
fix bug
joshua-kim Oct 18, 2024
e57464c
nit
joshua-kim Oct 18, 2024
d43dde9
nit
joshua-kim Oct 18, 2024
05f3d8d
wip
joshua-kim Oct 22, 2024
c6bd842
todo
joshua-kim Oct 23, 2024
1af3839
nit
joshua-kim Oct 23, 2024
bd2adb5
Add block assembler + executor (#1679)
aaronbuchwald Oct 23, 2024
0291e83
wip
joshua-kim Oct 23, 2024
51d3df3
nit
joshua-kim Oct 29, 2024
f492562
wip
joshua-kim Oct 30, 2024
56cb056
nit
joshua-kim Oct 30, 2024
27f9256
nit
joshua-kim Oct 30, 2024
762c133
nit
joshua-kim Oct 30, 2024
de4568c
nit
joshua-kim Oct 30, 2024
05aa9ee
ut
joshua-kim Oct 30, 2024
5d37d65
nit
joshua-kim Oct 30, 2024
a83f9cc
nit
joshua-kim Oct 30, 2024
0d4642f
nit
joshua-kim Oct 30, 2024
853173e
refactor
joshua-kim Oct 31, 2024
134734b
nit
joshua-kim Oct 31, 2024
46bdc1b
nit
joshua-kim Oct 31, 2024
6f2ec28
nit
joshua-kim Oct 31, 2024
326331a
nit
joshua-kim Oct 31, 2024
fb02516
nit
joshua-kim Oct 31, 2024
cfcf28c
nit
joshua-kim Nov 1, 2024
32acd66
nit
joshua-kim Nov 3, 2024
59bf05e
fix
joshua-kim Nov 4, 2024
47c89b0
nit
joshua-kim Nov 4, 2024
b77fd9f
nit
joshua-kim Nov 4, 2024
8c660f6
nit
joshua-kim Nov 4, 2024
82c083c
nit
joshua-kim Nov 4, 2024
512f38a
nit
joshua-kim Nov 4, 2024
45abfe2
nit
joshua-kim Nov 4, 2024
185e917
nit
joshua-kim Nov 4, 2024
14e99c8
nit
joshua-kim Nov 4, 2024
9ea33b1
nit
joshua-kim Nov 4, 2024
53d6863
clean diff
joshua-kim Nov 4, 2024
cb08022
clean diff
joshua-kim Nov 4, 2024
21b3f6e
nit
joshua-kim Nov 4, 2024
c8b6382
nit
joshua-kim Nov 4, 2024
4dee830
nit
joshua-kim Nov 4, 2024
8f9c48e
nit
joshua-kim Nov 4, 2024
86f5461
lint
joshua-kim Nov 4, 2024
db9c73f
nit
joshua-kim Nov 4, 2024
9743cdf
nit
joshua-kim Nov 4, 2024
8685221
nit
joshua-kim Nov 4, 2024
6feb271
Update dsmr/typed_client.go
joshua-kim Nov 5, 2024
b8956de
nit
joshua-kim Nov 5, 2024
51033da
move dsmr -> x
joshua-kim Nov 5, 2024
b0cb6c6
nit
joshua-kim Nov 5, 2024
76b4727
undo
joshua-kim Nov 5, 2024
9b1491b
nit
joshua-kim Nov 5, 2024
1957065
embed unsigned chunk
joshua-kim Nov 5, 2024
8a482b8
nit ut
joshua-kim Nov 5, 2024
ff84cd5
nits
joshua-kim Nov 5, 2024
2434d79
nit
joshua-kim Nov 5, 2024
c901b18
nit
joshua-kim Nov 5, 2024
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
74 changes: 74 additions & 0 deletions chain/assembler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package chain

import (
"context"
"time"

"github.com/ava-labs/hypersdk/utils"
)

type Assembler struct {
vm VM
}

func (a *Assembler) AssembleBlock(
ctx context.Context,
parent *StatefulBlock,
timestamp int64,
blockHeight uint64,
txs []*Transaction,
) (*StatefulBlock, error) {
ctx, span := a.vm.Tracer().Start(ctx, "chain.AssembleBlock")
defer span.End()

parentView, err := parent.View(ctx, true)
if err != nil {
return nil, err
}
parentStateRoot, err := parentView.GetMerkleRoot(ctx)
if err != nil {
return nil, err
}

blk := &StatelessBlock{
Prnt: parent.ID(),
Tmstmp: timestamp,
Hght: blockHeight,
Txs: txs,
StateRoot: parentStateRoot,
}
for _, tx := range txs {
blk.authCounts[tx.Auth.GetTypeID()]++
}

blkBytes, err := blk.Marshal()
if err != nil {
return nil, err
}
b := &StatefulBlock{
StatelessBlock: blk,
t: time.UnixMilli(blk.Tmstmp),
bytes: blkBytes,
accepted: false,
vm: a.vm,
id: utils.ToID(blkBytes),
}
return b, b.populateTxs(ctx) // TODO: simplify since txs are guaranteed to already be de-duplicated here
}

func (a *Assembler) ExecuteBlock(
ctx context.Context,
b *StatefulBlock,
) (*ExecutedBlock, error) {
ctx, span := a.vm.Tracer().Start(ctx, "chain.ExecuteBlock")
defer span.End()

if err := b.Verify(ctx); err != nil {
return nil, err
}

return NewExecutedBlockFromStateful(b), nil
}
90 changes: 90 additions & 0 deletions dsmr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# DSMR

## Chunks

Chunks wrap a group of containers (transactions) submitted by a single node. Each validator is responsible for building chunks locally with their own view of the mempool, request signatures from a threshold of the network to mark them as replicated, and distribute chunk certificates with >= 2f + 1 signatures that are eligible to be included in a block.

The network runs consensus over chunk-based blocks, where a block is valid if it contains only chunks with valid certificates (>=2f + 1 signatures). This enables validators to defer fetching the full chunk contents and execution until after the block has been accepted by the network.

### Chunk Rate Limiting

Chunks include a `Slot` (typically a timestamp), so that chunk verification (prior to chunk signing) can rate limit the number of chunks it's willing to sign within a certain period for a given validator. This requires the chunk server component to maintain some state about the amount of resources consumed by chunks it has signed for a given validator.

A simple example may be to allow each validator to produce a single chunk per `Slot`, so that if a validator attempts to create two conflicting chunks, they can produce at most one valid chunk certificate. Alternatively, chunk verification could utilize `fees.Dimensions` to describe the total resources consumed by chunks that it's produced and limit the amount of resources consumed by chunks from a single validator over a given period of time.

Note: conflicting chunks are a provable fault, so we could implement slashing here, but we omit it for now since they do not cause the network any harm.

### Chunk Expiry

After validators sign a chunk, they commit to store the chunk contents to guarantee availability if it's included in a block. However, there's no guarantee a signed chunk will be eventually included, so we handle garbage collection by marking chunks as expired once the blockchain's time has moved past the expiration (configurable) of the chunk slot.

## Breakdown

### Chunk Storage
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved

- Store non-persistent pool of chunk certificates
- Verify chunks, distribute signature shares, and commit to persist chunks we've signed
- Handle chunk acceptance / expiry by committing chunks to disk

TODO
- Switch from using Delete to DeleteRange where applicable
- Add retention limit for accepted chunk storage (can use DeleteRange here as well)
- Upper bound memory consumption
- Consider switching emap from `int64` to `uint64` to avoid unnecessary type conversions
- Support returning chunks in expiry order from `GatherChunkCertificates`

### P2P Client/Server w/ ACP-118 and Chunk Builder

- Implement P2P client/server functions for: `getChunk`, `putChunk`, `getSignatureShare`, `putSignatureShare`, and `putChunkCertificate`.
- Implement chunk builder that triggers chunk building and distribution via a channel (can be triggered via either sufficient transactions or timer)
- Migrate testing from storage to P2P layer, so that the tests run against the interface exported by DSMR

### Block Builder

- Implement `BuildBlock` that uses `GatherChunkCerts() []*ChunkCertificate` from the storage struct
- Return a `ChunkBlock` type that implements `snowman.Block`
- Verify verifies every chunk certificate
- Reject is a no-op that abandons the block (will need to handle duplicate chunks across processing chain of blocks)
- Accept should be a no-op until we've implemented the chunk block executor in the next stage

### Chunk Block Executor

- fetch and store any chunks that are not stored locally
- Filter duplicate transactions
- define block assembler interface to assemble, execute, and accept a block

```golang
type Assembler[T Tx, Block any, Result any] interface {
AssembleBlock(parentBlock Block, timestamp uint64, blockHeight uint64, txs []T) (Block, error)
ExecuteBlock(b Block) (Result, error)
}
```

The chunk block executor's only dependency is the ability to fetch chunks by ID. Since the client/server will wrap the storage backend, it should provide at least this interface:

```golang
type Backend interface {
GetChunks(chunkID []ids.ID) ([]*Chunk[T], error)
}
```

If `GetChunk` fails, it should be considered a fatal error since we must be able to fetch chunks for already accepted blocks. This means that the client implementation will need provide its own retry logic to fetch the chunk from the network.

Future TODOs:
- backpressure if the chain is moving faster than we can backfill chunks from accepted blocks
- apply fortification fees

Qs
- Should the assembled block use the parentID of the last assembled block (internal block) or the chunk block (wrapper block)?
- How should we handled failed chunk requests for an accepted block?
- Can the chunk certificate provide a hint to the p2p client/server to indicate where it can fetch the chunks from?

### Block Assembler + Executor / Integrate into HyperSDK w/ *chain.Block type

Define the `Assembler` and `Executor` types for the current `*chain.Block` type. The `Result` should be `*chain.ExecutedBlock`, so that we can pipe the result through to our current APIs that require `event.Subscription[*chain.ExecutedBlock]`.

### Swap Ghost Signatures / Certs for Warp Verification

- Switch from using empty implementations of `ChunkSignatureShare` and `ChunkCertificate` to using Warp signatures
- Add epoch'ed validator sets to improve stability (might be added directly to ProposerVM)

65 changes: 65 additions & 0 deletions dsmr/assembler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package dsmr

import (
"context"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"
)

// Note: Assembler breaks assembling and executing a block into two steps
// but these will be called one after the other.
type Assembler[T Tx, Block any, Result any] interface {
AssembleBlock(ctx context.Context, parentBlock Block, timestamp int64, blockHeight uint64, txs []T) (Block, error)
ExecuteBlock(ctx context.Context, b Block) (Result, error)
}

type ChunkGatherer[T Tx] interface {
// CollectChunks gathers the corresponding chunks and writes any chunks to
// storage that were not already persisted.
CollectChunks(chunkCerts []*ChunkCertificate) ([]*Chunk[T], error)
}

type BlockHandler[T Tx, Block any, Result any] struct {
lastAcceptedBlock Block
chunkGatherer ChunkGatherer[T]
Assembler Assembler[T, Block, Result]
}

func (b *BlockHandler[T, B, R]) Accept(ctx context.Context, block *Block) (R, error) {
// Collect and store chunks in the accepted block
chunks, err := b.chunkGatherer.CollectChunks(block.ChunkCerts)
if err != nil {
return *new(R), err
}

// Collect and de-duplicate txs
numTxs := 0
for _, chunk := range chunks {
numTxs += len(chunk.Txs)
}

txs := make([]T, 0, numTxs)
txSet := set.Set[ids.ID]{}
for _, chunk := range chunks {
for _, tx := range chunk.Txs {
txID := tx.GetID()
if txSet.Contains(txID) {
continue
}
txSet.Add(txID)
txs = append(txs, tx)
}
}

// Assemble and execute the block
assembledBlk, err := b.Assembler.AssembleBlock(ctx, b.lastAcceptedBlock, block.Timestamp, block.Height+1, txs)
if err != nil {
return *new(R), err
}
b.lastAcceptedBlock = assembledBlk
return b.Assembler.ExecuteBlock(ctx, assembledBlk)
}
Loading
Loading