Skip to content

Commit 903a7fc

Browse files
authored
Merge pull request #6 from ora-io/dev
Dev
2 parents 64ce151 + c3829f2 commit 903a7fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1552
-153
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# ORA STACK
22

33
## What is ORA STACK
4-
ORA Stack is a comprehensive framework designed for quickly setting up a web3 Oracle Service.
4+
ORA Stack is a comprehensive framework designed for quickly setting up a robust web3 Oracle Service.
55

66
It includes:
7-
- [Orap](./packages/orap/): the declarative signal-based backend framework;
8-
- [Reku](./packages/reku/): the reliable ethereum toolkit and utils;
9-
- [Utils](./packages/utils/): the common swiss-knife package.
7+
- [Orap](./packages/orap/): A Declarative Robust Oracle Backend Framework
8+
- [Reku](./packages/reku/): A Reliable Ethereum Kit & Utils
9+
- [Utils](./packages/utils/): A Common Swiss-knife Utils Package
1010

1111
Comming soon:
1212
- UAO: the async oracle framework

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ora-stack",
3-
"version": "0.0.8",
3+
"version": "0.1.0",
44
"private": true,
55
"packageManager": "[email protected]",
66
"description": "",

packages/orap/README.md

Lines changed: 250 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,191 @@
2323

2424
ORAP is a declarative framework for building oracle services, handy to use out of the box.
2525

26+
## Owl The Rapper
27+
> Show me you `Flow`s, I'll help you `assemble` to `Verse`s, which compose into a `Orap`.
28+
>
29+
> `drop` the `Beat`s, let's `play`!
30+
31+
`Orap` provides 2 styles of usage:
32+
- OO Style (Basic):
33+
- Use this framework as a basic tool set.
34+
- example: [customDemo](./examples/customDemo)
35+
- it's more flexible but cumbersome somehow.
36+
- e.g. you can use your own storage other than Redis and Memory, e.g. mysql etc., for caching.
37+
- you can define your own Task structure and handle workflow.
38+
- Declarative Style (Rap-lized):
39+
- Use this as a declarative *Rap-lized* framework, writing oracle services just like rapping! **Coding Like a Rapper**
40+
- example: [declarativeDemo](./examples/declarativeDemo)
41+
- it's way more easy to implement, `Orap` handles most of the common part, e.g. signal handle, task defining, task caching, task fetch and processing, multitasks processing, etc., while it may sacrifice flexibility in some way.
42+
43+
Back in the scene, there are 2 internal layers in `Orap`:
44+
45+
- Basic Layer:
46+
- mainly referring to the `Signal`, `StoreManager`, and `Task`, where the concepts are self-explained in engineering context.
47+
- it can be used directly by users.
48+
- *Rap-lized* Layer:
49+
- mainly referring to the `Flow`, `Verse`, and `Beat`, where the concepts are introduced by `Orap` only.
50+
- it helps to build the declarative functionality, which is way easier for users and save some developers.
51+
- it mostly for internal developing purpose, and ~~should be~~ easy to scale and extend, though user also has access to them if they want.
52+
53+
54+
> About Multi-chain: Currently Each `Orap` listens to only 1 blockchain network by design, similar to http servers. Create multiple `Orap` instances to implement multi-chain listener.
55+
>
56+
> Suggest to include network in task `prefix()`, to avoid key collision in cache store
57+
58+
2659
## Usage
60+
61+
### Declarative Style (Rap-lized)
62+
63+
It comes with rich features like customized task cache, multitasks handling etc.
64+
65+
Note the following already includes using Redis as the store to cache tasks, allowing continuation after service restart, it's robust even when the service restarts.
66+
2767
```ts
28-
import { ListenOptions, Orap, StoreManager } from '../../orap'
29-
import { memoryStore, redisStore } from '../../utils'
68+
import { ethers } from 'ethers'
69+
import { Orap, StoreManager } from '@orap-io/orap'
70+
import { Logger, redisStore } from '@ora-io/utils'
71+
72+
// new orap
73+
const orap = new Orap()
74+
75+
// use redis
76+
const store = redisStore()
77+
const sm = new StoreManager(store)
78+
79+
// use a logger
80+
const logger = new Logger('info', '[orap-raplize-sample]')
81+
82+
const handle1 = (...args: any) => { logger.log('handle task 1', args); return true }
83+
84+
const handle2 = (...args: any) => { logger.log('handle task 2', args); return true }
85+
86+
// define event signal with crosscheck, and customized cacheable tasks
87+
// note: use redis as the cache layer
88+
orap.event(eventSignalParam)
89+
.crosscheck(ccOptions)
90+
// add a task
91+
.task()
92+
.cache(sm)
93+
.prefix('ora-stack:orap:raplizeSample:Task-1:', 'ora-stack:orap:raplizeSample:Done-Task-1:')
94+
.ttl({ taskTtl: 120000, doneTtl: 60000 })
95+
.handle(handle1)
96+
// add another task
97+
.another()
98+
.task()
99+
.cache(sm)
100+
.prefix('ora-stack:orap:raplizeSample:Task-2:', 'ora-stack:orap:raplizeSample:Done-Task-2:')
101+
.ttl({ taskTtl: 120000, doneTtl: 60000 })
102+
.handle(handle2)
103+
104+
// set logger before listen
105+
orap.logger(logger)
106+
107+
// start signal listeners
108+
orap.listen(
109+
{
110+
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
111+
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
112+
},
113+
() => { console.log('listening on provider.network') }
114+
)
115+
```
116+
117+
#### Orap Flow
118+
119+
Each `new Orap()` starts a `Orap Flow`
120+
121+
**.event(eventSignalParam, handlFn)**
122+
- `eventSignalParam`: defines an event signal and enters an Event Flow
123+
- `handlFn`: customized hook on new event received.
124+
- `return true` to continue the rest of processes
125+
- `return false` to hijack the rest of processes
126+
127+
**.listen(options, onListenFn?)**
128+
- `options`:
129+
- required: wsProvider, for subscription
130+
- optional: httpProvider, for crosscheck only, since crosscheck is based on getLogs
131+
- `onListenFn`: customized hook when listener started.
132+
133+
**.logger(logger)**
134+
- set which logger to use across this orap
135+
136+
#### Event Flow
137+
138+
Each `.event(...)` starts an `Event Flow`
139+
140+
**.crosscheck(...)**
141+
- set an automated crosschecker for this event, to ensure the missing events of subscription will always be caught by `getLogs`.
142+
- this can mitigate the common unstable nature of WebSocket rpc providers and increase the service availability.
143+
144+
**.task()**
145+
- add a task for this event type
146+
- starts a `Task Flow`
147+
148+
**.handle(handlFn)**
149+
- same as `.event(.., handlFn)`
150+
- `handlFn`: customized hook on new event received.
151+
- `return true` to continue the rest of processes
152+
- `return false` to hijack the rest of processes
153+
154+
**.another()**
155+
- back to the parent `Orap Flow`, so that it can add another `.event`
156+
- e.g. `orap.event(...).another().event(...)`
157+
158+
#### Task Flow
159+
160+
Each `.task(...)` starts a `Task Flow`
161+
162+
**.handle(handler: HandleFn)**
163+
- set the task handler, the most important property for a task.
164+
- `return true` to identify handle success, and entering `onSuccess`
165+
- `return false` to identify handle failed, and entering `onSuccess`
166+
167+
**.cache(sm: StoreManager)**
168+
- set the store to cache the tasks
169+
- default: use memory as the cache layer
170+
171+
**.prefix(taskPrefix: Prefix, donePrefix: Prefix)**
172+
- set the prefix of tasks in the store cache for management
173+
- `donePrefix`: prefix of 'todo' tasks records
174+
- `donePrefix`: prefix of 'done' tasks records
175+
- default: "Task:" & "Done-Task:"
176+
177+
**.ttl({ taskTtl, doneTtl }: { taskTtl: number; doneTtl: number })**
178+
- set the ttl of tasks in the store cache for management
179+
- `donePrefix`: ttl of 'todo' tasks records
180+
- `donePrefix`: ttl of 'done' tasks records
181+
- default: no limit
182+
183+
**.key(toKey: ToKeyFn)**
184+
- defines the primary key of a task based on the event values (i.e. log topics)
185+
- default: random hex string
186+
187+
**.success(onSuccess: HandleResultFn)**
188+
- defines how to process the task if the handler success
189+
- default: remove the 'todo' task from & set the 'done' task record to the cache store
190+
191+
**.fail(onFail: HandleResultFn)**
192+
- defines how to process the task if the handler success
193+
- default: remove the 'todo' task from (ignore task)
194+
195+
**.context(ctx: Context)**
196+
- optional: set the context that can be accessed to task functions
197+
198+
**.another()**
199+
- back to the parent `Event Flow`, so that it can add another `.task`
200+
- e.g. `orap.event(...).task().another().task()`
201+
202+
### OO Style (Basic)
203+
204+
Note the following doesn't include task cache, it only calls `handle` every time it receives an event. So this service is only for demo, don't use it for production, otherwise it may miss events when service down.
205+
206+
```ts
207+
import { ethers } from 'ethers'
208+
import { Orap } from '@orap-io/orap'
30209

31210
const orap = new Orap()
32-
// const sm = new StoreManager(redisStore(...)) // use redis
33-
const sm = new StoreManager() // use memory
34211

35212
const eventSignalParam = {
36213
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
@@ -44,37 +221,85 @@ orap.event(eventSignalParam, handle)
44221
.crosscheck({ pollingInterval: 1000, batchBlocksCount: 1, blockInterval: 12000 })
45222

46223
orap.listen(
47-
{ wsProvider: 'wss://127.0.0.1', httpProvider: 'http://127.0.0.1' },
224+
{
225+
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
226+
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
227+
},
48228
() => { console.log('listening on provider.network') }
49229
)
50230
```
51231

52-
### listen options
53-
- required: wsProvider, for subscription
54-
- optional: httpProvider, for crosscheck only, since crosscheck is based on getLogs
232+
## *Rap-lized* Layer
55233

56-
## Task
234+
The following terminology is internally, can be transparent to users.
57235

58-
### TaskBase
59-
- provide universal `toString`, `fromString`, `stringify`
236+
- A `Orap` compromises multiple `Verses` as the processors;
237+
- Some `Verses` includes `Beat`s, which define the pace and the incoming signals that triggering task handling in Orap.
238+
- For users want to build a `Orap`: only need to define `Flow`s **intuitively**, the Owl Rapper will take care of all the rest things.
60239

61-
### TaskStorable
62-
- provide store (redis) compatible features, i.e. load, save, remove, done
63-
- overwrite when extends:
64-
- `toKey()` (required): define the primary key that identifies each task, **doesn't** include `taskPrefix`
65-
- `taskPrefix` (recommend): set the prefix of all tasks, also is used when `load` task
66-
- `taskPrefixDone` (recommend): set the prefix of finished tasks, only used in `done`; no need to set if you don't use "task.done(sm)"
240+
**Terminology**
67241

68-
## Signal
242+
- `Flow`:
243+
- handling user-defined option flows,
244+
- e.g. user can define following flows:
245+
```typescript
246+
new Orap().event(..).crosscheck()
247+
.handle(..)
248+
.task(..).key(..).prefix(..).ttl(..)
249+
.handle(..)
250+
.another()
251+
.task(..).key(..).prefix(..).ttl(..)
252+
.handle(..)
253+
```
254+
- `Flow.assemble()`:
255+
- wrap up the `Flow` definition and build a `Verse` based on it.
256+
- `Verse`:
257+
- equivalent to an executor/processor of the corresponding `Flow`.
258+
- `Verse.play()`:
259+
- equivalent to start/launch the executor/processor.
260+
- `Beat`:
261+
- a wrap of the `Signal`, which defines the incoming triggers that initiate the runtime process flow
262+
- e.g. `EventBeat` defines the event listener
263+
- `Beat` wraps `Signal` into a uniformed class with only the `constructor` and `drop()`, easy for `Verse` to handle
264+
- **Beats Drives the Song!**
265+
- `Beat.drop()`:
266+
- start the `Signal` listener process.
267+
- **Drop the Beats!**
69268

70-
all actions that arrive the oracle server and trigger actions are defined as signal, including:
71-
- [x] event
72-
- [ ] block
269+
## Basic Layer
270+
271+
Basic Layer currently consists of 3 parts:
272+
- `Signal` defines the incoming trigger types
273+
- `Task` defines the task types that handles signals
274+
- `StorageManager` defines the cache interface, allowing tasks to be cached
275+
276+
### Signal
277+
278+
All events that arrive the oracle service and trigger following actions are defined as `Signal`, including:
279+
- [x] `EventSignal`
280+
- [ ] `BlockSignal`
73281
- [ ] http request
74282
etc.
75283

76-
### EventSignal
284+
**EventSignal**
77285
- define event listener as simple as: `orap.event({address:"0x", abi:"", eventName: "Transfer"}, handleSignal)`
78-
- provide crosschecker by `reku`, available config please checkout `AutoCrossCheckParam` in `reku`
79-
- currently one and only one crosschecker is set to each event signal
80-
- store: provide 2 options: use memory or redis, checkout `orap/store`
286+
- natively integrate `crosschecker` features from [@ora-io/reku](https://github.com/ora-io/ora-stack/blob/main/packages/reku/), available config please check out `AutoCrossCheckParam` in `reku`
287+
- each event signal only accept at most one crosschecker.
288+
- `callback`: the user provided handle function to handle the new signals.
289+
290+
### Task
291+
292+
**TaskBase**
293+
- provide universal `toString`, `fromString`, `stringify`
294+
295+
**TaskStorable**
296+
- provide store compatible features, i.e. load, save, remove, done
297+
- overwrite when extends:
298+
- `toKey()` (required): define the primary key that identifies each task, **doesn't** include `taskPrefix`
299+
- `taskPrefix` (recommend): set the prefix of all tasks, also is used when `load` task
300+
- `taskPrefixDone` (recommend): set the prefix of finished tasks, only used in `done`; no need to set if you don't use "task.done(sm)"
301+
302+
### StorageManager
303+
- a wrap class designed for caching tasks in Orap
304+
- `store`: the store entity, currently provides 2 options: use memory or redis, checkout `orap/store`
305+
- `queryDelay`: when doing retry-able operations, e.g. get all keys with the given prefix, this defines the interval between retries.

packages/orap/beat/event.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Logger } from '@ora-io/utils'
2+
import type { AutoCrossCheckParam, Providers } from '@ora-io/reku'
3+
import type { EventSignalCallback, EventSignalRegisterParams } from '../signal'
4+
import { EventSignal } from '../signal'
5+
6+
/**
7+
* Beat is Rap-lized, i.e. formalized in Orap framework, version of Signal
8+
* only Signals have corresponding Beat class, task & orap don't have
9+
*/
10+
export class EventBeat extends EventSignal {
11+
constructor(
12+
params: EventSignalRegisterParams,
13+
callback: EventSignalCallback,
14+
logger: Logger,
15+
crosscheckOptions: Omit<AutoCrossCheckParam, 'address' | 'topics' | 'onMissingLog'> | undefined,
16+
private subscribeProvider: Providers,
17+
private crosscheckProvider: Providers | undefined,
18+
) {
19+
super(params, callback, logger, crosscheckOptions)
20+
}
21+
22+
drop() {
23+
this.listen(this.subscribeProvider, this.crosscheckProvider)
24+
}
25+
}

packages/orap/beat/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { EventBeat } from './event'

packages/orap/mock/demo/app.ts renamed to packages/orap/examples/customDemo/app.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ import { TransferTask } from './taskTransfer'
88

99
// new orap
1010
const orap = new Orap()
11-
// set to app specific logger
12-
orap.setLogger(logger)
11+
orap.logger(logger)
1312

1413
let store: any
1514
let sm: any
1615

17-
export function startDemo(options: ListenOptions, storeConfig?: any) {
16+
export function startCustomDemo(options: ListenOptions, storeConfig?: any) {
1817
store = redisStore(storeConfig) // use redis
1918
// store = memoryStore(storeConfig); // use memory
2019
sm = new StoreManager(store)

packages/orap/mock/demo/taskTransfer.ts renamed to packages/orap/examples/customDemo/taskTransfer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ export class TransferTask extends TaskStorable {
3535

3636
static async load<T extends TaskStorable>(this: Constructor<T>, sm: any): Promise<T> {
3737
const task = await (this as any)._load(sm)
38-
logger.log('[*] load task', task.toKey())
38+
logger.log('[*] load task', await task.toKey())
3939
return task
4040
}
4141

4242
async done(sm: any) {
43-
logger.log('[*] done task', this.toKey())
43+
logger.log('[*] done task', await this.toKey())
4444
await super.done(sm)
4545
}
4646

4747
async remove(sm: any) {
48-
logger.log('[*] remove task', this.toKey())
48+
logger.log('[*] remove task', await this.toKey())
4949
await super.remove(sm)
5050
}
5151
}

0 commit comments

Comments
 (0)