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

feat(lib/runtime): Implement ext_offchain_http_request_start_version_1 host function #1947

Merged
merged 30 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2899a29
feat: implement offchain http host functions
EclesioMeloJunior Oct 28, 2021
8f7a581
chore: decoding Result<i16, ()>
EclesioMeloJunior Oct 29, 2021
4d6167f
chore: adjust result encoding/decoding
EclesioMeloJunior Oct 31, 2021
faa5752
chore: add export comment on Get
EclesioMeloJunior Oct 31, 2021
e08300e
chore: change to map and update test wasm
EclesioMeloJunior Nov 2, 2021
70e6351
chore: use request id buffer
EclesioMeloJunior Nov 2, 2021
ad8c0bb
chore: change to NewHTTPSet
EclesioMeloJunior Nov 2, 2021
5606599
chore: add export comment
EclesioMeloJunior Nov 2, 2021
f2ca718
Merge branch 'development' into eclesio/offchain-http
EclesioMeloJunior Nov 2, 2021
81c0fb9
chore: use pkg/scale to encode Result to wasm memory
EclesioMeloJunior Nov 2, 2021
6da5ad8
Merge branch 'eclesio/offchain-http' of github.com:ChainSafe/gossamer…
EclesioMeloJunior Nov 2, 2021
fb21f11
chore: update naming and fix lint warns
EclesioMeloJunior Nov 2, 2021
881fc51
Merge branch 'development' into eclesio/offchain-http
EclesioMeloJunior Nov 2, 2021
7444590
Merge branch 'development' into eclesio/offchain-http
EclesioMeloJunior Nov 3, 2021
8896ae5
Merge branch 'development' into eclesio/offchain-http
EclesioMeloJunior Nov 3, 2021
ee372c4
chore: use buffer.put when remove http request
EclesioMeloJunior Nov 3, 2021
78bfd0b
chore: add more comments
EclesioMeloJunior Nov 3, 2021
42ff50e
chore: add unit tests
EclesioMeloJunior Nov 3, 2021
13d540a
chore: fix misspelling
EclesioMeloJunior Nov 3, 2021
60a1f11
chore: fix scale marshal to encode Result instead of Option<Result>
EclesioMeloJunior Nov 3, 2021
4cd379f
chore: ignore uneeded error
EclesioMeloJunior Nov 4, 2021
f1130ad
chore: fix unused params
EclesioMeloJunior Nov 4, 2021
5f980ab
chore: cannot remove unused params
EclesioMeloJunior Nov 4, 2021
c8913d2
chore: ignore deepsource errors
EclesioMeloJunior Nov 4, 2021
fcb5da9
chore: add parallel to wasmer tests
EclesioMeloJunior Nov 4, 2021
dd89472
chore: remove dereferencing
EclesioMeloJunior Nov 5, 2021
8624e09
chore: fix param compatibility
EclesioMeloJunior Nov 8, 2021
427c183
chore: embed mutex iunto httpset struct
EclesioMeloJunior Nov 8, 2021
750caa9
chore: update the hoost polkadot test runtime location
EclesioMeloJunior Nov 9, 2021
1598499
chore: fix request not available test
EclesioMeloJunior Nov 9, 2021
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
2 changes: 1 addition & 1 deletion lib/runtime/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const (
// v0.9 test API wasm
HOST_API_TEST_RUNTIME = "hostapi_runtime"
HOST_API_TEST_RUNTIME_FP = "hostapi_runtime.compact.wasm"
HOST_API_TEST_RUNTIME_URL = "https://github.com/ChainSafe/polkadot-spec/blob/9cc27bf7b7f21c106000103f8f6b6c51f7fb8353/test/runtimes/hostapi/hostapi_runtime.compact.wasm?raw=true"
HOST_API_TEST_RUNTIME_URL = "https://github.com/ChainSafe/polkadot-spec/blob/838a050dfe394a733e0ec95427289750fea8e381/test/hostapi_runtime.compact.wasm?raw=true"

// v0.8 substrate runtime with modified name and babe C=(1, 1)
DEV_RUNTIME = "dev_runtime"
Expand Down
103 changes: 103 additions & 0 deletions lib/runtime/offchain/httpset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package offchain

import (
"errors"
"net/http"
"sync"
)

const maxConcurrentRequests = 1000

var (
errIntBufferEmpty = errors.New("int buffer exhausted")
errIntBufferFull = errors.New("int buffer is full")
errRequestIDNotAvailable = errors.New("request id not available")
)

// requestIDBuffer created to control the amount of available non-duplicated ids
type requestIDBuffer chan int16

// newIntBuffer creates the request id buffer starting from 1 till @buffSize (by default @buffSize is 1000)
func newIntBuffer(buffSize int16) *requestIDBuffer {
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to return a pointer? you can change the receiver methods below to be (b requestIDBuffer), then you don't need to dereference it in the methods

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

b := make(chan int16, buffSize)
for i := int16(1); i <= buffSize; i++ {
b <- i
}

intb := requestIDBuffer(b)
return &intb
}

func (b *requestIDBuffer) get() (int16, error) {
select {
case v := <-*b:
return v, nil
default:
return 0, errIntBufferEmpty
}
}

func (b *requestIDBuffer) put(i int16) error {
select {
case *b <- i:
return nil
default:
return errIntBufferFull
}
}

// HTTPSet holds a pool of concurrent http request calls
type HTTPSet struct {
mtx *sync.Mutex
Copy link
Contributor

@noot noot Nov 8, 2021

Choose a reason for hiding this comment

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

you can embed this, so you don't need to call p.mtx.Lock(), you can just call p.Lock() and same for Unlock()

Suggested change
mtx *sync.Mutex
sync.Mutex
// etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

reqs map[int16]*http.Request
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a chance this map will be accesses concurrently? I think potentially

Copy link
Member Author

Choose a reason for hiding this comment

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

yes! There is mutexes locks in StartRequest and Remove and I've added locks on Get tho

idBuff *requestIDBuffer
}

// NewHTTPSet creates a offchain http set that can be used
// by runtime as HTTP clients, the max concurrent requests is 1000
func NewHTTPSet() *HTTPSet {
return &HTTPSet{
mtx: new(sync.Mutex),
reqs: make(map[int16]*http.Request),
idBuff: newIntBuffer(maxConcurrentRequests),
}
}

// StartRequest create a new request using the method and the uri, adds the request into the list
// and then return the position of the request inside the list
func (p *HTTPSet) StartRequest(method, uri string) (int16, error) {
p.mtx.Lock()
defer p.mtx.Unlock()

id, err := p.idBuff.get()
if err != nil {
return 0, err
}

if _, ok := p.reqs[id]; ok {
return 0, errRequestIDNotAvailable
}

req, err := http.NewRequest(method, uri, nil)
if err != nil {
return 0, err
}

p.reqs[id] = req
return id, nil
}

// Remove just remove a expecific request from reqs
func (p *HTTPSet) Remove(id int16) error {
p.mtx.Lock()
defer p.mtx.Unlock()

delete(p.reqs, id)

return p.idBuff.put(id)
}

// Get returns a request or nil if request not found
func (p *HTTPSet) Get(id int16) *http.Request {
return p.reqs[id]
}
47 changes: 47 additions & 0 deletions lib/runtime/offchain/httpset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package offchain

import (
"net/http"
"testing"

"github.com/stretchr/testify/require"
)

const defaultTestURI = "http://example.url"

func TestHTTPSetLimit(t *testing.T) {
t.Parallel()

set := NewHTTPSet()
var err error
for i := 0; i < maxConcurrentRequests+1; i++ {
_, err = set.StartRequest(http.MethodGet, defaultTestURI)
}

require.ErrorIs(t, errIntBufferEmpty, err)
}

func TestHTTPSet_StartRequest_NotAvailableID(t *testing.T) {
t.Parallel()

set := NewHTTPSet()
set.reqs[0] = &http.Request{}

_, err := set.StartRequest(http.MethodGet, defaultTestURI)
require.ErrorIs(t, errRequestIDNotAvailable, err)
}

func TestHTTPSetGet(t *testing.T) {
t.Parallel()

set := NewHTTPSet()

id, err := set.StartRequest(http.MethodGet, defaultTestURI)
require.NoError(t, err)

req := set.Get(id)
require.NotNil(t, req)

require.Equal(t, http.MethodGet, req.Method)
require.Equal(t, defaultTestURI, req.URL.String())
}
18 changes: 10 additions & 8 deletions lib/runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package runtime
import (
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/keystore"
"github.com/ChainSafe/gossamer/lib/runtime/offchain"
log "github.com/ChainSafe/log15"
)

Expand Down Expand Up @@ -72,14 +73,15 @@ type InstanceConfig struct {

// Context is the context for the wasm interpreter's imported functions
type Context struct {
Storage Storage
Allocator *FreeingBumpHeapAllocator
Keystore *keystore.GlobalKeystore
Validator bool
NodeStorage NodeStorage
Network BasicNetwork
Transaction TransactionState
SigVerifier *SignatureVerifier
Storage Storage
Allocator *FreeingBumpHeapAllocator
Keystore *keystore.GlobalKeystore
Validator bool
NodeStorage NodeStorage
Network BasicNetwork
Transaction TransactionState
SigVerifier *SignatureVerifier
OffchainHTTPSet *offchain.HTTPSet
}

// NewValidateTransactionError returns an error based on a return value from TaggedTransactionQueueValidateTransaction
Expand Down
36 changes: 34 additions & 2 deletions lib/runtime/wasmer/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ package wasmer
//
// extern int32_t ext_trie_blake2_256_root_version_1(void *context, int64_t a);
// extern int32_t ext_trie_blake2_256_ordered_root_version_1(void *context, int64_t a);
// extern int32_t ext_trie_blake2_256_verify_proof_version_1(void *context, int32_t a, int64_t b, int64_t c, int64_t d);
// extern int32_t ext_trie_blake2_256_verify_proof_version_1(void *context, int64_t a, int64_t b, int64_t c, int64_t d);
//
// extern int64_t ext_misc_runtime_version_version_1(void *context, int64_t a);
// extern void ext_misc_print_hex_version_1(void *context, int64_t a);
Expand Down Expand Up @@ -89,6 +89,7 @@ package wasmer
// extern int64_t ext_offchain_submit_transaction_version_1(void *context, int64_t a);
// extern int64_t ext_offchain_timestamp_version_1(void *context);
// extern void ext_offchain_sleep_until_version_1(void *context, int64_t a);
// extern int64_t ext_offchain_http_request_start_version_1(void *context, int64_t a, int64_t b, int64_t c);
//
// extern void ext_storage_append_version_1(void *context, int64_t a, int64_t b);
// extern int64_t ext_storage_changes_root_version_1(void *context, int64_t a);
Expand Down Expand Up @@ -894,7 +895,7 @@ func ext_trie_blake2_256_ordered_root_version_1(context unsafe.Pointer, dataSpan
}

//export ext_trie_blake2_256_verify_proof_version_1
func ext_trie_blake2_256_verify_proof_version_1(context unsafe.Pointer, a C.int32_t, b, c, d C.int64_t) C.int32_t {
func ext_trie_blake2_256_verify_proof_version_1(context unsafe.Pointer, a, b, c, d C.int64_t) C.int32_t { // skipcq: RVV-B0012
logger.Debug("[ext_trie_blake2_256_verify_proof_version_1] executing...")
logger.Warn("[ext_trie_blake2_256_verify_proof_version_1] unimplemented")
return 0
Expand Down Expand Up @@ -1675,6 +1676,33 @@ func ext_offchain_sleep_until_version_1(_ unsafe.Pointer, deadline C.int64_t) {
logger.Warn("unimplemented")
}

//export ext_offchain_http_request_start_version_1
func ext_offchain_http_request_start_version_1(context unsafe.Pointer, methodSpan, uriSpan, metaSpan C.int64_t) C.int64_t { // skipcq: RVV-B0012
logger.Debug("executing...")

instanceContext := wasm.IntoInstanceContext(context)

httpMethod := asMemorySlice(instanceContext, methodSpan)
uri := asMemorySlice(instanceContext, uriSpan)

result := scale.NewResult(int16(0), nil)

runtimeCtx := instanceContext.Data().(*runtime.Context)
reqID, err := runtimeCtx.OffchainHTTPSet.StartRequest(string(httpMethod), string(uri))

if err != nil {
logger.Error("failed to start request", "error", err)
_ = result.Set(scale.Err, nil)
} else {
_ = result.Set(scale.OK, reqID)
}

enc, _ := scale.Marshal(result)
ptr, _ := toWasmMemory(instanceContext, enc)

return C.int64_t(ptr)
}

func storageAppend(storage runtime.Storage, key, valueToAppend []byte) error {
nextLength := big.NewInt(1)
var valueRes []byte
Expand Down Expand Up @@ -2334,6 +2362,10 @@ func ImportsNodeRuntime() (*wasm.Imports, error) { //nolint
if err != nil {
return nil, err
}
_, err = imports.Append("ext_offchain_http_request_start_version_1", ext_offchain_http_request_start_version_1, C.ext_offchain_http_request_start_version_1)
if err != nil {
return nil, err
}
_, err = imports.Append("ext_sandbox_instance_teardown_version_1", ext_sandbox_instance_teardown_version_1, C.ext_sandbox_instance_teardown_version_1)
if err != nil {
return nil, err
Expand Down
Loading