Background
I am using NestJS and the Observable<AxiosResponse>
pattern with the HttpModule
to "observe" and eventually forward values returned by a JSON-RPC server, in this case a Blockchain node.
In case Blockchains are unfamiliar, they can be thought of as a linked list, where each new element in the list points to the previous element, something like this:
let blockchain = [
[0,{value: 'something', previous: None}],
[1,{value: 'somethingelse', previous: 0}], ...
[N,{value: 'somethingsomething', previous: N-1}]
]
Sometimes a "fork" can happen in a blockchain system. It would look something like the below tree:
[A] <-- [B]<--- [C]
|
--- [C'] <--- [D] <--- [E] <--- ... and so on
Problem
If my NestJS application gets Block [A]
at time 0, block [B]
at time 1 and block [C]
at time 2, but suddenly, at time 3 I get [E]
, I would not get block [D]
and [C']
. This means that I would be unable to inspect the values
in these two missing blocks.
Logic
Because all blocks have a pointer to a previous block, I do have the ability to retrieve block [D]
by simply passing the pointer to it from block [E]
. Similarly, from block [D]
I could subsequently get block [C']
. Because [C']
has a pointer to [B]
I would have successfully retrieved all missing blocks.
I am quite new to Observables
so I am not entirely sure of how I can backtrack recursively when I use the NestJS HttpModule
in this way:
export class BlockchainService {
private url: string;
private top?: number;
constructor(private httpService: HttpService,
private config: ConfigService) {
this.url = this.config.get<string>('my_blockchain_url')
}
getBestBlockHash(): Observable<AxiosResponse<any>> {
return this.httpService.post(this.url, {
"method" : "getbestblockhash"
})
}
getBestBlock(): Observable<AxiosResponse<any>> {
return this.getBestBlockHash().pipe(
mergeMap((hash) => this.getBlock(hash.data.result))
)
}
getBlock(hash: string): Observable<AxiosResponse<any>> {
return this.httpService.post(this.url, {
"method" : "getblock",
"params" : {
"blockhash" : hash
}
})
}
Attempt 1
Because the Observable
holds the block data, I cannot evaluate whether to backtrack, or not without subscribe
to it, or pipe
it.
Using Mrk Sef's proposal below and the iif()
operator, seems to take me further since I can pass the getBestBlock()
Observable
as a parameter to a checkBackTrack
function leveraging iif()
as follows:
checkBackTrack(obs: Observable<AxiosResponse<any>>): Observable<AxiosResponse<any>> {
let diff: number
let previoushash: string
console.log(diff, previoushash)
obs.pipe(tap(block => {
diff = this.top - block.data.result.height
previoushash = block.data.result.previoushash
}))
console.log(diff, previoushash)
const backTrackNeeded = iif(
() => diff > 0,
this.backTrack(diff, previoushash),
obs
)
return backTrackNeeded;
}
where the backTrakc
function looks like:
backTrack(n: number, previoushash: string): Observable<AxiosResponse<any>> {
return (n < 0) ? EMPTY : this.getBlock(previoushash).pipe(
switchMap(previousBlock => this.backTrack(n-1, previousBlock.data.result.previousblockhash)),
)
}
allows me to do the following: this.checkBackTrack(this.getBestBlock())
.
However, I am unable to define diff
and previoushash
in the checkBackTrack
function... Also, this introduces side effects, which I do not want.