forked from rollkit/rollkit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from vitwit/vitwit/rollkit-da-adapter
Integrate avail da adapter in rollkit
- Loading branch information
Showing
4 changed files
with
354 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
package avail | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
|
||
ds "github.com/ipfs/go-datastore" | ||
|
||
"errors" | ||
|
||
"github.com/rollkit/rollkit/da" | ||
"github.com/rollkit/rollkit/log" | ||
"github.com/rollkit/rollkit/types" | ||
|
||
gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4" | ||
"github.com/centrifuge/go-substrate-rpc-client/v4/signature" | ||
|
||
v4types "github.com/centrifuge/go-substrate-rpc-client/v4/types" | ||
) | ||
|
||
const BLOCK_NOT_FOUND = "\"Not found\"" | ||
const PROCESSING_BLOCK = "\"Processing block\"" | ||
|
||
// Config stores Avail DALC configuration parameters. | ||
type Config struct { | ||
BaseURL string `json:"base_url"` | ||
Seed string `json:"seed"` | ||
ApiURL string `json:"api_url"` | ||
AppDataURL string `json:"app_data_url"` | ||
AppID int `json:"app_id"` | ||
Confidence float64 `json:"confidence"` | ||
} | ||
|
||
// DataAvailabilityLayerClient uses Avail-Node configuration parameters | ||
type DataAvailabilityLayerClient struct { | ||
_ types.NamespaceID | ||
config Config | ||
logger log.Logger | ||
} | ||
|
||
// Confidence stores block params retireved from Avail Light Node Endpoint | ||
type Confidence struct { | ||
Block uint32 `json:"block"` | ||
Confidence float64 `json:"confidence"` | ||
SerialisedConfidence *string `json:"serialised_confidence,omitempty"` | ||
} | ||
|
||
// AppData stores Extrinsics retrieved from Avail Light Node Endpoint | ||
type AppData struct { | ||
Block uint32 `json:"block"` | ||
Extrinsics []string `json:"extrinsics"` | ||
} | ||
|
||
var _ da.DataAvailabilityLayerClient = &DataAvailabilityLayerClient{} | ||
var _ da.BlockRetriever = &DataAvailabilityLayerClient{} | ||
|
||
// Init initializes DataAvailabilityLayerClient instance. | ||
func (c *DataAvailabilityLayerClient) Init(_ types.NamespaceID, config []byte, kvStore ds.Datastore, logger log.Logger) error { | ||
c.logger = logger | ||
|
||
if len(config) > 0 { | ||
return json.Unmarshal(config, &c.config) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Start prepares DataAvailabilityLayerClient to work. | ||
func (c *DataAvailabilityLayerClient) Start() error { | ||
|
||
c.logger.Info("starting avail data availability layer client", "baseURL", c.config.ApiURL) | ||
|
||
return nil | ||
} | ||
|
||
// Stop stops DataAvailabilityLayerClient. | ||
func (c *DataAvailabilityLayerClient) Stop() error { | ||
|
||
c.logger.Info("stopping avail data availability layer client") | ||
|
||
return nil | ||
} | ||
|
||
// SubmitBlock submits a block to DA layer. | ||
func (c *DataAvailabilityLayerClient) SubmitBlocks(ctx context.Context, blocks []*types.Block) da.ResultSubmitBlocks { | ||
|
||
for _, block := range blocks { | ||
data, err := block.MarshalBinary() | ||
if err != nil { | ||
return da.ResultSubmitBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
if err := SubmitData(c.config.ApiURL, c.config.Seed, c.config.AppID, data); err != nil { | ||
return da.ResultSubmitBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
|
||
} | ||
|
||
return da.ResultSubmitBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusSuccess, | ||
Message: "success", | ||
DAHeight: 1, | ||
}, | ||
} | ||
|
||
} | ||
|
||
// RetrieveBlocks gets the block from DA layer. | ||
func (c *DataAvailabilityLayerClient) RetrieveBlocks(ctx context.Context, dataLayerHeight uint64) da.ResultRetrieveBlocks { | ||
|
||
blocks := []*types.Block{} | ||
|
||
Loop: | ||
blockNumber := dataLayerHeight | ||
|
||
appDataURL := fmt.Sprintf(c.config.BaseURL+c.config.AppDataURL, blockNumber) | ||
|
||
// Sanitize and validate the URL | ||
parsedURL, err := url.Parse(appDataURL) | ||
if err != nil { | ||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
|
||
// Create an HTTP request with the sanitized URL | ||
req, err := http.NewRequest("GET", parsedURL.String(), nil) | ||
if err != nil { | ||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
|
||
// Perform the HTTP request | ||
client := http.DefaultClient | ||
response, err := client.Do(req) | ||
if err != nil { | ||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
defer func() { | ||
_ = response.Body.Close() | ||
}() | ||
|
||
responseData, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
|
||
var appDataObject AppData | ||
if string(responseData) == BLOCK_NOT_FOUND { | ||
|
||
appDataObject = AppData{Block: uint32(blockNumber), Extrinsics: []string{}} | ||
|
||
} else if string(responseData) == PROCESSING_BLOCK { | ||
|
||
goto Loop | ||
|
||
} else { | ||
if err = json.Unmarshal(responseData, &appDataObject); err != nil { | ||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusError, | ||
Message: err.Error(), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
txnsByteArray := []byte{} | ||
for _, extrinsic := range appDataObject.Extrinsics { | ||
txnsByteArray = append(txnsByteArray, []byte(extrinsic)...) | ||
} | ||
|
||
block := &types.Block{ | ||
SignedHeader: types.SignedHeader{ | ||
Header: types.Header{ | ||
BaseHeader: types.BaseHeader{ | ||
Height: blockNumber, | ||
}, | ||
}}, | ||
Data: types.Data{ | ||
Txs: types.Txs{txnsByteArray}, | ||
}, | ||
} | ||
|
||
blocks = append(blocks, block) | ||
|
||
return da.ResultRetrieveBlocks{ | ||
BaseResult: da.BaseResult{ | ||
Code: da.StatusSuccess, | ||
DAHeight: uint64(appDataObject.Block), | ||
}, | ||
Blocks: blocks, | ||
} | ||
} | ||
|
||
func SubmitData(apiURL string, seed string, appID int, data []byte) error { | ||
|
||
// if app id is greater than 0 then it must be created before submitting data | ||
if appID < 1 { | ||
return errors.New("AppID cant be 0") | ||
} | ||
|
||
api, err := gsrpc.NewSubstrateAPI(apiURL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
meta, err := api.RPC.State.GetMetadataLatest() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c, err := v4types.NewCall(meta, "DataAvailability.submit_data", data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Create the extrinsic | ||
ext := v4types.NewExtrinsic(c) | ||
|
||
genesisHash, err := api.RPC.Chain.GetBlockHash(0) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rv, err := api.RPC.State.GetRuntimeVersionLatest() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
keyringPair, err := signature.KeyringPairFromSecret(seed, 42) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
key, err := v4types.CreateStorageKey(meta, "System", "Account", keyringPair.PublicKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var accountInfo v4types.AccountInfo | ||
ok, err := api.RPC.State.GetStorageLatest(key, &accountInfo) | ||
if err != nil || !ok { | ||
return errors.New("failed to get the latest storage") | ||
} | ||
|
||
nonce := uint32(accountInfo.Nonce) | ||
signOptions := v4types.SignatureOptions{ | ||
BlockHash: genesisHash, | ||
Era: v4types.ExtrinsicEra{IsMortalEra: false}, | ||
GenesisHash: genesisHash, | ||
Nonce: v4types.NewUCompactFromUInt(uint64(nonce)), | ||
SpecVersion: rv.SpecVersion, | ||
Tip: v4types.NewUCompactFromUInt(0), | ||
AppID: v4types.NewUCompactFromUInt(uint64(appID)), | ||
TransactionVersion: rv.TransactionVersion, | ||
} | ||
|
||
// Sign the transaction | ||
err = ext.Sign(keyringPair, signOptions) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Send the extrinsic | ||
_, err = api.RPC.Author.SubmitExtrinsic(ext) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.