Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions actions/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package actions

const (
AnchorRegisterComputeUnits = 1
)

const (
// pre-defined actions starts from 0xf0
AnchorRegisterID uint8 = 0xf0
)
30 changes: 30 additions & 0 deletions actions/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package actions

import (
"encoding/binary"

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

const (
AnchorRegisteryPrefix = 0xf0
AnchorPrefix = 0xf1
)

const AnchorChunks uint16 = 1

func AnchorRegistryKey() string {
// state key must >= 2 bytes
k := make([]byte, 1+consts.Uint16Len)
k[0] = AnchorRegisteryPrefix
binary.BigEndian.PutUint16(k[1:], AnchorChunks) //TODO: update the BalanceChunks to AnchorChunks
return string(k)
}

func AnchorKey(namespace []byte) string {
k := make([]byte, 1+len(namespace)+consts.Uint16Len)
k[0] = AnchorPrefix
copy(k[1:], namespace[:])
binary.BigEndian.PutUint16(k[1+len(namespace):], AnchorChunks) //TODO: update the BalanceChunks to AnchorChunks
return string(k)
}
52 changes: 52 additions & 0 deletions actions/structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package actions

import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/utils"
)

// TODO: do we need to rename it to `RollupInfo`
type AnchorInfo struct {
FeeRecipient codec.Address `json:"feeRecipient"`
Namespace []byte `json:"namespace"`
}

func NewAnchorInfo(namespace []byte, feeRecipient codec.Address) *AnchorInfo {
return &AnchorInfo{
FeeRecipient: feeRecipient,
Namespace: namespace,
}
}

func (a *AnchorInfo) ID() ids.ID {
return utils.ToID([]byte(a.Namespace))
}

func (a *AnchorInfo) Size() int {
return codec.AddressLen + codec.BytesLen(a.Namespace)
}

func (a *AnchorInfo) Marshal(p *codec.Packer) error {
p.PackBytes(a.Namespace)
p.PackAddress(a.FeeRecipient)

if err := p.Err(); err != nil {
return err
}

return nil
}

func UnmarshalAnchorInfo(p *codec.Packer) (*AnchorInfo, error) {
ret := new(AnchorInfo)

// p := codec.NewReader(raw, consts.NetworkSizeLimit)
p.UnpackBytes(32, false, &ret.Namespace) // TODO: set limit for the length of namespace bytes
p.UnpackAddress(&ret.FeeRecipient)

if err := p.Err(); err != nil {
return nil, err
}
return ret, nil
}
28 changes: 28 additions & 0 deletions actions/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package actions

import (
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
)

func PackNamespaces(namespaces [][]byte) ([]byte, error) {
p := codec.NewWriter(len(namespaces)*8, consts.NetworkSizeLimit)
p.PackInt(len(namespaces))
for _, ns := range namespaces {
p.PackBytes(ns)
}
return p.Bytes(), p.Err()
}

func UnpackNamespaces(raw []byte) ([][]byte, error) {
p := codec.NewReader(raw, consts.NetworkSizeLimit)
nsLen := p.UnpackInt(false)
namespaces := make([][]byte, 0, nsLen)
for i := 0; i < nsLen; i++ {
ns := make([]byte, 0, 8)
p.UnpackBytes(-1, false, &ns)
namespaces = append(namespaces, ns)
}

return namespaces, p.Err()
}
89 changes: 89 additions & 0 deletions anchor/anchor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package anchor

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)

// TODO: this file to define anchor client

type AnchorClient struct {
Url string `json:"url"`
vm VM
}

func NewAnchorClient(vm VM, url string) *AnchorClient {
url = strings.TrimRight(url, "/")
return &AnchorClient{
vm: vm,
Url: url,
}
}

// TODO: implement client methods below
func (cli *AnchorClient) GetHeaderV2(slot int64) (*SEQHeaderResponse, error) {
// slot := 0
parentHash := "0x0"
pubkey := "0x0"
numToBTxs := 5
numRoBChains := 1
numRobChunkTxs := 10

path := fmt.Sprintf("/eth/v1/builder/header2/%d/%s/%s/%d/%d/%d", slot, parentHash, pubkey, numToBTxs, numRoBChains, numRobChunkTxs)
url := cli.Url + path

var client http.Client

resp, err := client.Get(url)
if err != nil {
return nil, err
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var header SEQHeaderResponse
if err := json.Unmarshal(bodyBytes, &header); err != nil {
return nil, err
}

return &header, nil
}

func (cli *AnchorClient) GetPayloadV2(slot int64) (*SEQPayloadResponse, error) {
path := "/eth/v1/builder/blinded_blocks2"
url := cli.Url + path

req := SEQPayloadRequest{
Slot: uint64(slot),
}

reqBytes, err := json.Marshal(req)
if err != nil {
return nil, err
}

var client http.Client
resp, err := client.Post(url, "application/json", bytes.NewBuffer(reqBytes))
if err != nil {
return nil, err
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var payload SEQPayloadResponse
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
return nil, err
}

return &payload, nil
}
47 changes: 47 additions & 0 deletions anchor/anchor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package anchor

import (
"fmt"
"testing"

ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)

const ANCHOR_URL = "http://localhost:18550"

type MockVM struct{}

func TestAnchorFlow(t *testing.T) {
slot := int64(1)
cli := NewAnchorClient(nil, ANCHOR_URL)
header, err := cli.GetHeaderV2(slot)
require.NoError(t, err)
fmt.Printf("%+v\n", header)

payload, err := cli.GetPayloadV2(slot)
require.NoError(t, err)

tobPayload := payload.ToBPayload
txs := tobPayload.Transactions
fmt.Printf("ToB txs: ")
for _, txRaw := range txs {
var tx ethtypes.Transaction
err := tx.UnmarshalBinary(txRaw)
require.NoError(t, err)
chainID := tx.ChainId()
fmt.Printf("txHash: %s\tchainID: %d\n", tx.Hash().Hex(), chainID.Int64())
}

for chainID, robPayload := range payload.RoBPayloads {
fmt.Printf("RoB-%s txs\n", chainID)
txs := robPayload.Transactions
for _, txRaw := range txs {
var tx ethtypes.Transaction
err := tx.UnmarshalBinary(txRaw)
require.NoError(t, err)
chainID := tx.ChainId()
fmt.Printf("txHash: %s\tchainID: %d\n", tx.Hash().Hex(), chainID.Int64())
}
}
}
13 changes: 13 additions & 0 deletions anchor/dependencies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package anchor

import (
"context"

"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/codec"
)

type VM interface {
HandleAnchorChunk(ctx context.Context, meta *chain.AnchorMeta, slot int64, txs []*chain.Transaction, priorityFeeReceiverAddr codec.Address) error
SignAnchorDigest(ctx context.Context, digest []byte) ([]byte, error)
}
36 changes: 36 additions & 0 deletions anchor/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package anchor

import (
"sync"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/hypersdk/actions"
)

type AnchorRegistry struct {
anchors map[ids.ID]*actions.AnchorInfo
anchorsL sync.Mutex
vm VM
}

func NewAnchorRegistry(vm VM) *AnchorRegistry {
return &AnchorRegistry{
vm: vm,
anchors: make(map[ids.ID]*actions.AnchorInfo),
}
}

func (r *AnchorRegistry) Update(info []*actions.AnchorInfo) {
r.anchorsL.Lock()
defer r.anchorsL.Unlock()

r.anchors = make(map[ids.ID]*actions.AnchorInfo, len(info))
for _, a := range info {
id := a.ID()
r.anchors[id] = a
}
}

func (r *AnchorRegistry) Len() int {
return len(r.anchors)
}
74 changes: 74 additions & 0 deletions anchor/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package anchor

import (
"github.com/attestantio/go-eth2-client/spec/bellatrix"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/hypersdk/codec"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)

type Data = hexutil.Bytes

type SEQPayloadRequest struct {
Slot uint64 `json:"slot"`
ToBBlindedBeaconBlock AnchorSignedBlindedBeaconBlock `json:"tobblindedbeaconblock"`
RoBBlindedBeaconBlocks map[string]AnchorSignedBlindedBeaconBlock `json:"robblindedbeaconblocks"`
}

type SEQHeaderResponse struct {
Slot uint64 `json:"slot"`
// nodeID of chunk producing validator.
Producer ids.NodeID `json:"producer"`
// block builder address
PriorityFeeReceiverAddr codec.Address `json:"priorityfeereceiveraddr"`
// hash of the anchor chunks (tob + robs)
ChunkHash phase0.Hash32 `json:"chunkhash"`
ToBHash phase0.Hash32 `json:"tobhash"`
RoBHashes map[string]phase0.Hash32 `json:"robhashes"`
}

type AnchorSignedBlindedBeaconBlock struct {
Message *AnchorBlindedBeaconBlock
Signature phase0.BLSSignature `ssz-size:"96"`
}

type AnchorBlindedBeaconBlock struct {
Slot phase0.Slot
ProposerIndex phase0.ValidatorIndex
ParentRoot phase0.Root `ssz-size:"32"`
StateRoot phase0.Root `ssz-size:"32"`
Body *AnchorBlindedBeaconBlockBody
}

type AnchorBlindedBeaconBlockBody struct {
ExecutionPayloadHeader *AnchorExecutionPayloadHeader
}

// receiving payload from SEQ
type AnchorExecutionPayloadHeader struct {
FeeRecipient bellatrix.ExecutionAddress `ssz-size:"20"`
StateRoot [32]byte `ssz-size:"32"`
ReceiptsRoot [32]byte `ssz-size:"32"`
LogsBloom [256]byte `ssz-size:"256"`
BlockNumber uint64
Timestamp uint64
BlockHash phase0.Hash32 `ssz-size:"32"`
TransactionsRoot phase0.Root `ssz-size:"32"`
ChunkDigest phase0.Root `ssz-size:"32"`
}

type SEQPayloadResponse struct {
Slot uint64 `json:"slot"`
ToBPayload ExecutionPayload2 `json:"tobpayload"`
RoBPayloads map[string]ExecutionPayload2 `json:"robpayloads"`
}

type ExecutionPayload2 struct {
Slot uint64 `json:"slot"`
BlockHash common.Hash `json:"blockHash"`
// Array of transaction objects, each object is a byte list (DATA) representing
// TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718
Transactions []Data `json:"transactions"`
}
Loading