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

Minor changes for Arcadia #26

Merged
merged 25 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 24 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ audited.
The `SeqVM` is built from the ground up with the shared sequencer built directly into the chain
enabling decentralization from the start. This enable users to easily send messages from rollups like NodeKit chain to the shared sequencer. The contents of `SequencerMSG` is just `ChainId|Data|FromAddress` where the data is the transaction data
from the rollup translated into a byte[].
### Arcadia
Rollups using Arcadia, will be able to produce blocks with sub second block times without losing synchronous atomic composability with other rollups using Arcadia, all thanks to Arcadia's ability to preconf rollup block chunks in 250ms and make the block available for rollups to pull instantly after preconfing.

> Read more about arcadia [here](./arcadia.md).

## Demos
The first step to running these demos is to launch your own `SeqVM` Subnet. You
Expand Down Expand Up @@ -59,7 +63,7 @@ When you are done, the output should look something like this:
database: .seq-cli
address: seq1qrzvk4zlwj9zsacqgtufx7zvapd3quufqpxk5rsdd4633m4wz2fdjlydh3t
chainID: Em2pZtHr7rDCzii43an2bBi1M2mTFyLN33QP1Xfjy7BcWtaH9
assetID (use DSEQ for native seq): 27grFs9vE2YP9kwLM5hQJGLDvqEY9ii71zzdoRHNGC4Appavug
assetID (use SEQ for native seq): 27grFs9vE2YP9kwLM5hQJGLDvqEY9ii71zzdoRHNGC4Appavug
balance: 10000 27grFs9vE2YP9kwLM5hQJGLDvqEY9ii71zzdoRHNGC4Appavug
```

Expand Down
9 changes: 5 additions & 4 deletions actions/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"

hactions "github.com/AnomalyFi/hypersdk/actions"
"github.com/AnomalyFi/hypersdk/chain"
"github.com/AnomalyFi/hypersdk/codec"
"github.com/AnomalyFi/hypersdk/consts"
Expand Down Expand Up @@ -51,7 +52,7 @@ func (*Auction) GetTypeID() uint8 {
return AuctionID
}

func (a *Auction) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
func (a *Auction) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.BalanceKey(a.AuctionInfo.BuilderSEQAddress)): state.Read | state.Write,
string(storage.BalanceKey(ArcadiaFundAddress())): state.All,
Expand All @@ -60,7 +61,7 @@ func (a *Auction) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
}

func (*Auction) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks, storage.BalanceChunks, storage.EpochExitsChunks}
return []uint16{storage.BalanceChunks, storage.BalanceChunks, hactions.EpochExitsChunks}
}

// This is a permissioned action, only authorized address can pass the execution.
Expand Down Expand Up @@ -92,7 +93,7 @@ func (a *Auction) Execute(
msg := make([]byte, 16)
binary.BigEndian.PutUint64(msg[:8], a.AuctionInfo.EpochNumber)
binary.BigEndian.PutUint64(msg[8:], a.AuctionInfo.BidPrice)
msg = append(msg, a.BuilderPublicKey...)
msg = append(msg, a.AuctionInfo.BuilderSEQAddress[:]...)
sig, err := bls.SignatureFromBytes(a.BuilderSignature)
if err != nil {
return nil, fmt.Errorf("failed to parse signature: %w", err)
Expand Down Expand Up @@ -129,7 +130,7 @@ func (*Auction) ComputeUnits(codec.Address, chain.Rules) uint64 {
return AuctionComputeUnits
}

func (a *Auction) Size() int {
func (*Auction) Size() int {
return 2*consts.Uint64Len + bls.PublicKeyLen + bls.SignatureLen + codec.AddressLen
}

Expand Down
1 change: 0 additions & 1 deletion actions/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package actions
const (
TransferID uint8 = 0
MsgID uint8 = 1
ExitID uint8 = 2
AuctionID uint8 = 3
)

Expand Down
22 changes: 11 additions & 11 deletions actions/epoch_exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,33 @@
)

type EpochExit struct {
Info storage.EpochInfo `json:"info"`
Epoch uint64 `json:"epoch"`
OpCode int `json:"opcode"`
Info hactions.EpochInfo `json:"info"`
Epoch uint64 `json:"epoch"`
OpCode int `json:"opcode"`
manojkgorle marked this conversation as resolved.
Show resolved Hide resolved
}

func (*EpochExit) GetTypeID() uint8 {
return ExitID
return hactions.EpochExitID
}

func (e *EpochExit) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
func (e *EpochExit) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.EpochExitsKey(e.Epoch)): state.All,
string(hactions.EpochExitsKey(e.Epoch)): state.All,
string(storage.RollupInfoKey(e.Info.Namespace)): state.Read,
string(storage.RollupRegistryKey()): state.Read,
}
}

func (*EpochExit) StateKeysMaxChunks() []uint16 {
return []uint16{storage.EpochExitsChunks, hactions.RollupInfoChunks, hactions.RollupRegistryChunks}
return []uint16{hactions.EpochExitsChunks, hactions.RollupInfoChunks, hactions.RollupRegistryChunks}
}

// TODO: Add check for curr epoch > start epoch of arcadia.
func (e *EpochExit) Execute(
ctx context.Context,
_ chain.Rules,
mu state.Mutable,
ts int64,
_ int64,
_ uint64,
actor codec.Address,
_ ids.ID,
Expand Down Expand Up @@ -95,8 +95,8 @@
}
}
if !exists {
epochExits = new(storage.EpochExitInfo)
epochExits.Exits = make([]*storage.EpochInfo, 0)
epochExits = new(hactions.EpochExitInfo)
epochExits.Exits = make([]*hactions.EpochInfo, 0)
}
epochExits.Exits = append(epochExits.Exits, &e.Info)
if err := storage.SetEpochExits(ctx, mu, e.Epoch, epochExits); err != nil {
Expand All @@ -117,7 +117,7 @@
if idx == -1 {
return nil, fmt.Errorf("exit not found, namespace: %s, epoch: %d", hex.EncodeToString(e.Info.Namespace), e.Epoch)
}
epochExits.Exits = slices.Delete(epochExits.Exits, idx, idx+1)

Check failure on line 120 in actions/epoch_exit.go

View workflow job for this annotation

GitHub Actions / Lint

cannot infer S (/opt/hostedtoolcache/go/1.21.13/x64/src/slices/slices.go:218:13) (typecheck)
if err := storage.SetEpochExits(ctx, mu, e.Epoch, epochExits); err != nil {
return nil, err
}
Expand All @@ -144,7 +144,7 @@

func UnmarshalEpochExit(p *codec.Packer) (chain.Action, error) {
var epoch EpochExit
info, err := storage.UnmarshalEpochInfo(p)
info, err := hactions.UnmarshalEpochInfo(p)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions actions/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ var (
ErrNameSpaceNotRegistered = errors.New("namespace not registered")
ErrNotAuthorized = errors.New("not authorized")
ErrNameSpaceAlreadyRegistered = errors.New("namespace already registered")
ErrExitEpochSmallerThanStartEpoch = errors.New("exit epoch is smaller than start epoch")
)
77 changes: 41 additions & 36 deletions actions/rollup_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"encoding/hex"
"fmt"
"slices"

hactions "github.com/AnomalyFi/hypersdk/actions"
"github.com/AnomalyFi/hypersdk/chain"
Expand All @@ -20,22 +19,21 @@ var _ chain.Action = (*RollupRegistration)(nil)

const (
CreateRollup = iota
DeleteRollup
ExitRollup
UpdateRollup
)

type RollupRegistration struct {
Info hactions.RollupInfo `json:"info"`
Namespace []byte `json:"namespace"`
StartEpoch uint64 `json:"startEpoch"`
OpCode int `json:"opcode"`
Info hactions.RollupInfo `json:"info"`
Namespace []byte `json:"namespace"`
OpCode int `json:"opcode"`
}

func (*RollupRegistration) GetTypeID() uint8 {
return hactions.RollupRegisterID
}

func (r *RollupRegistration) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
func (r *RollupRegistration) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.RollupInfoKey(r.Namespace)): state.All,
string(storage.RollupRegistryKey()): state.All,
Expand All @@ -46,6 +44,8 @@ func (*RollupRegistration) StateKeysMaxChunks() []uint16 {
return []uint16{hactions.RollupInfoChunks, hactions.RollupRegistryChunks}
}

// TODO: this action needs to be managed by DAO to manage deletions since we are not deleting any namespace from storage
// but only by marking them as regsitered or exited
func (r *RollupRegistration) Execute(
ctx context.Context,
rules chain.Rules,
Expand All @@ -65,45 +65,56 @@ func (r *RollupRegistration) Execute(

namespaces, err := storage.GetRollupRegistry(ctx, mu)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get namespaces: %s", err.Error())
}

switch r.OpCode {
case CreateRollup:
if contains(namespaces, r.Namespace) {
return nil, ErrNameSpaceAlreadyRegistered
}
if r.StartEpoch < Epoch(hght, rules.GetEpochLength())+2 {
return nil, fmt.Errorf("epoch number is not valid, minimum: %d, actual: %d", Epoch(hght, rules.GetEpochLength())+2, r.StartEpoch)
if r.Info.StartEpoch < Epoch(hght, rules.GetEpochLength())+2 || r.Info.ExitEpoch != 0 {
return nil, fmt.Errorf("epoch number is not valid, minimum: %d, actual: %d, exit: %d", Epoch(hght, rules.GetEpochLength())+2, r.Info.StartEpoch, r.Info.ExitEpoch)
}
namespaces = append(namespaces, r.Namespace)
if err := storage.SetRollupInfo(ctx, mu, r.Namespace, &r.Info); err != nil {
return nil, err
return nil, fmt.Errorf("unable to set rollup info(CREATE): %s", err.Error())
}
case UpdateRollup:
// only allow modifing informations that are not related to ExitEpoch or StartEpoch
if err := authorizationChecks(ctx, actor, namespaces, r.Namespace, mu); err != nil {
return nil, err
return nil, fmt.Errorf("authorization failed(UPDATE): %s", err.Error())
}

rollupInfoExists, err := storage.GetRollupInfo(ctx, mu, r.Namespace)
if err != nil {
return nil, fmt.Errorf("unable to get existing rollup info(UPDATE): %s", err.Error())
}

// rewrite epoch info
r.Info.ExitEpoch = rollupInfoExists.ExitEpoch
r.Info.StartEpoch = rollupInfoExists.StartEpoch

if err := storage.SetRollupInfo(ctx, mu, r.Namespace, &r.Info); err != nil {
return nil, err
return nil, fmt.Errorf("unable to set rollup info(UPDATE): %s", err.Error())
}
case DeleteRollup:
case ExitRollup:
if err := authorizationChecks(ctx, actor, namespaces, r.Namespace, mu); err != nil {
return nil, err
return nil, fmt.Errorf("unable to set rollup info(EXIT): %s", err.Error())
}

nsIdx := -1
for i, ns := range namespaces {
if bytes.Equal(r.Namespace, ns) {
nsIdx = i
break
}
rollupInfoExists, err := storage.GetRollupInfo(ctx, mu, r.Namespace)
if err != nil {
return nil, fmt.Errorf("unable to get existing rollup info(UPDATE): %s", err.Error())
}
namespaces = slices.Delete(namespaces, nsIdx, nsIdx+1)
if r.Info.ExitEpoch < rollupInfoExists.StartEpoch || r.Info.ExitEpoch < Epoch(hght, rules.GetEpochLength())+2 {
return nil, fmt.Errorf("(EXIT)epoch number is not valid, minimum: %d, actual: %d, start: %d", Epoch(hght, rules.GetEpochLength())+2, r.Info.ExitEpoch, rollupInfoExists.StartEpoch)
}

// overwrite StartEpoch
r.Info.StartEpoch = rollupInfoExists.StartEpoch

if err := storage.DelRollupInfo(ctx, mu, r.Namespace); err != nil {
return nil, err
if err := storage.SetRollupInfo(ctx, mu, r.Namespace, &r.Info); err != nil {
return nil, fmt.Errorf("unable to set rollup info(EXIT): %s", err.Error())
}
default:
return nil, fmt.Errorf("op code(%d) not supported", r.OpCode)
Expand All @@ -127,24 +138,18 @@ func (r *RollupRegistration) Marshal(p *codec.Packer) {
r.Info.Marshal(p)
p.PackBytes(r.Namespace)
p.PackInt(r.OpCode)
p.PackUint64(r.StartEpoch)
}

func UnmarshalRollupRegister(p *codec.Packer) (chain.Action, error) {
var RollupReg RollupRegistration
var rollupReg RollupRegistration
info, err := hactions.UnmarshalRollupInfo(p)
if err != nil {
return nil, err
}
RollupReg.Info = *info
p.UnpackBytes(-1, false, &RollupReg.Namespace)
RollupReg.OpCode = p.UnpackInt(false)
if RollupReg.OpCode == CreateRollup {
RollupReg.StartEpoch = p.UnpackUint64(true)
} else {
RollupReg.StartEpoch = p.UnpackUint64(false)
}
return &RollupReg, nil
rollupReg.Info = *info
p.UnpackBytes(-1, false, &rollupReg.Namespace)
rollupReg.OpCode = p.UnpackInt(false)
return &rollupReg, nil
}

func (*RollupRegistration) ValidRange(chain.Rules) (int64, int64) {
Expand Down
2 changes: 1 addition & 1 deletion actions/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ func authorizationChecks(ctx context.Context, actor codec.Address, namespaces []
}

func Epoch(blockHeight uint64, epochLength int64) uint64 {
return uint64(int64(blockHeight) / epochLength)
return blockHeight / uint64(epochLength)
}
47 changes: 47 additions & 0 deletions arcadia.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Arcadia

Arcadia facilitates the Shared Block Production on The Composable Network for participating Rollups. Block Production on arcadia is divided into 12s(6 SEQ blocks) Epochs with a single block builder building blocks for all the participating rollups in that Epoch. Winning block builder is selected through a Ahead Of Time Auction on Arcadia. Rollup blocks are built as chunks and preconfed by validators. All txs in preconfed chunks will be included in SEQ blocks in later time.

## Rollups:
- Rollups opt for shared block production by arcadia ahead of time from a given epoch and added to list of participating rollups for the shared auction.
- Rollups get synchronous atomic composability for the blocks built during the participating epoch.
- Once opt in for shared block production, rollups continue to exist in the list of participating rollups for future epochs, until they entirely opt out or opt out for a specific epoch.
- Rollups opt in and out entirely through `RegisterRollup` action tx.
- Rollups opt out for an epoch through `EpochExit` action tx.

## Ahead Of Time Auction:
- Auction for rights to build blocks for participating rollups in Epoch K starts one Epoch earlier and ends with the proudction of 4th block of SEQ in Epoch K-1.
- Rollups can Opt In or Out for Epoch K, before the end of Epoch K-2. The list of participating rollups for Epoch K finalises with the end of Epoch K-2 and builders bid for rights to build blocks for all participating rollups, based on the perceived value accural for Epoch K and private order flow deals.
- With 4th block of SEQ in Epoch k-1 produced, winning bid is sent to be included in next SEQ block as `Auction` action tx, to deduct bid amount from the builder.
- All participating entities are notified about the bid winner, once the `Auction` action tx makes through a SEQ block.

## Chunks:
- In arcadia txs get preconfed as chunks, rather than blocks.
- There are two defined chunk types: i. Top Of Block(ToB) ii. Rest Of Block(RoB). A chunk consists a set of txs or tx bundles, depending on it being a ToB or RoB.
- SEQ supports a single rollup tx or cross chain bundle in a single tx as multi-action tx is supported.
- A ToB contains cross rollup bundles only. But necessarily a ToB need not contain txs for all participating rollups, but can contain any subset of the list. A RoB contains txs for a single rollup.
- Chunks get preconfed by validators and made available for rollups to pull.
- A Rollup block can have multiple ToB chunks, but only one RoB chunk.

## Block Builders:
- Block builders participate in auction after confirming their ability to build blocks for participating rollups.
- They build ToB chunks for different subsets of participating rollups and RoB chunks for each rollup, and send them with rollup block numbers and chunk nonce to arcadia continuosly to get them preconfed by SEQ validators.
- Every rollup block, ideally contains many ToBs and one RoB. ToBs are ordered by the chunk nonce included along with the chunk followed by RoB.

ToB chunks containig different subset of rollups:
<p align="center">
<img src="./assets/tob.png" width="40%">
</p>

A rollup block built by builder:
<p align="center">
<img src="./assets/rollup_block.png" width="50%">
</p>
## SEQ Validators:
- SEQ validators register with arcadia to receive chunks for preconfing.
- SEQ validators check few assertions on chunks, and issue a chunk cert if the assertions are satisfied.
- Valdiators store the signature verified txs in a emap, to prevent duplicate txs and ease signature verification while accepting the block.
- SEQ blocks are produced every 2 seconds, validators fetch `SequencerMsg` txs payload from arcadia to get included in the SEQ block and fill the rest of the block with `non SequencerMsg` txs.

## E2E Flow:
![E2E flow](./assets/e2e.png)
Binary file added assets/e2e.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/rollup_block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/tob.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions auth/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import "github.com/AnomalyFi/hypersdk/vm"

// Note: Registry will error during initialization if a duplicate ID is assigned. We explicitly assign IDs to avoid accidental remapping.
const (
ed25519ID uint8 = 0
Ed25519ID uint8 = 0
BLSID uint8 = 1
)

func Engines() map[uint8]vm.AuthEngine {
return map[uint8]vm.AuthEngine{
ed25519ID: &ED25519AuthEngine{},
Ed25519ID: &ED25519AuthEngine{},
}
}
4 changes: 2 additions & 2 deletions auth/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (d *ED25519) address() codec.Address {
}

func (*ED25519) GetTypeID() uint8 {
return ed25519ID
return Ed25519ID
}

func (*ED25519) ComputeUnits(chain.Rules) uint64 {
Expand Down Expand Up @@ -153,5 +153,5 @@ func (b *ED25519Batch) Done() []func() error {
}

func NewED25519Address(pk ed25519.PublicKey) codec.Address {
return codec.CreateAddress(ed25519ID, utils.ToID(pk[:]))
return codec.CreateAddress(Ed25519ID, utils.ToID(pk[:]))
}
Loading
Loading