체인의정석

UniswapV3 백엔드 구축하며, 틱 단위 계산에 사용한 다시 볼 것 같은 함수들 정리 (tickSpace 맞추기, 데이터 쌓는 지점 체크 & 동일 블록에서의 값 합산, Promise.all의 중첩 사용) 본문

개발/backend

UniswapV3 백엔드 구축하며, 틱 단위 계산에 사용한 다시 볼 것 같은 함수들 정리 (tickSpace 맞추기, 데이터 쌓는 지점 체크 & 동일 블록에서의 값 합산, Promise.all의 중첩 사용)

체인의정석 2023. 8. 24. 18:55
728x90
반응형

tickSpace 맞추기

tick별로 계산할 때 모든 틱을 다 관리할 수 없기에 uniswap V3에서 tick Space 단위로 맞출 때 올림 내림 처리를 해주어야 한다.

각 풀마다 tick이 다르다보니 각 풀별 기본 tickSpace를 컨트렉트 콜로 불러와서 사용해주어야 한다.

function adjustedStartTick(tick, tickSpace) {
    return Math.floor(tick / tickSpace) * tickSpace;
}

function adjustedEndTick(tick, tickSpace) {
    return Math.ceil(tick / tickSpace) * tickSpace;
}

// Test
console.log(adjustedStartTick(14, 10));      // Outputs: 10
console.log(adjustedEndTick(14, 10));        // Outputs: 20
console.log(adjustedStartTick(1003242, 1000)); // Outputs: 1003000
console.log(adjustedEndTick(1003242, 1000));  // Outputs: 1004000

Promise.all의 중첩 사용
Promise.all 안에 다시 await가 있다면 그 안에 다시 Promise.all을 사용해야 한다.

하지만 Promise.all 을 2중으로 겹치는 것은 복잡해지고 에러의 위치파악을 어렵게 한다.

따라서 구조적으로 분리를 하는 것이 좋으며 

//Before

//외부의 경우 Promise.all 을 사용하여서 모든 결과 값을 리턴하도록 함
//DB접근 시 발생한 에러를 잡기 위해 밖은 예외 처리로 한번 감싸주기
await Promise.all(Array.map(async (entity) => {
	//내부적으로 DB접근 시에는 일반 map을 써서 비동기 함수 형태로 병렬 실행
    //DB접근 시에 예외처리 해주고 에러 발생
    await Promise.all(
        insideArray.map(async (insideData) => {
            return await this.queryFunction();
        })
    )
    return true;
}))

따라서 위와 같이 짜인 코드를

아래와 같이 쓰고 예외처리 구문도 추가하는 식으로 코드를 바꾸었다.

//After

async function insidePromise(entity) {
	//try catch 추가
    await Promise.all(
        insideArray.map(async (insideData) => {
            return await this.queryFunction(insideData.A, insideData.B);
        })
    )
}


await Promise.all(Array.map(async (entity) => {
	//try catch 추가
	await insidePromise(entity)
    return true;
}))

 

데이터 쌓는 지점 체크 & 동일 블록에서의 값 합산

블록번호를 지정하여 조건을 체크하는 방식으로 한번 저장된 블록이 다시 들어올 경우 저장되지 않도록 한다.

하나의 블록안에 여러 수치에 대한 값이 있기 때문에 업데이트나 입력 전에 블록별로 한번에 묶어서 계산해 주는 과정이 필요하다.

이런식으로 동일 블록에서의 값을 합살 할때는 따로 객체를 하나 만들어서 key값을 지정한 후에 업데이트 해주고 리턴 값으로 Objects.values를 통하여 줘서 객체를 다시 배열 형태로 만들고 키 값을 제거하는 식으로 묶어서 계산하고 값을 리턴해 줄 수 있다.

    mergeVolumeByTick(2darray) {
        // Flatten the 2D array into 1D
        const flattenedData = [].concat(...2darray);
        const object = {};
        // Merge volumes by tick
        flattenedData.forEach(item => {
            if (object[item.tick]) {
                if(object[item.tick].block_number < item.block_number) {
                    object[item.tick].block_number = item.block_number;
                }
                object[item.tick].volume += item.volume;
            } else {
                object[item.tick] = {
                    block_number: item.block_number,
                    tick: item.tick,
                    volume: item.volume
                };
            }
        });
        return Object.values(tickMap);
    }

예를 들어 object라는 객체를 만들고 그 안에다가 key값을 tick으로 두고 tick에 따른 block_number와 volume을 더해준다면 위와 같이 각 틱을 키로 두고 계산하여 값을 더해가면서 블록번호를 업데이트 해 줄 수 있다.

그리고 결과 값에서 객체의 값들만 리턴하는 식으로하여서 틱으로 둔 키 값을 제거하고 원하는 형태로 병합된 데이터를 넘겨 줄 수 있다.

해당 데이터를 만든 후에 쿼리로 저장할 때 또한 쿼리에 필요한 값들을 다음과 같이 넘길 수 있다.

여기서는 키 값이 2개가 나오는데 이렇게 2개의 키 값을 통해서 병합을 하게 된다면 block_number와 volume 2개의 조건절이 있을때 insert할 값들 여러개를 뽑아서 가지고 있다가 한번에 저장시킬 수 있다.

    mergeSameTickData(data) {
        const result = data.reduce((acc, entity) => {
            //중복을 체크하기 위하여 내부적으로 사용하는 key값
            const key = `${data.block_number}-${data.volume}`;
        
            if (!acc[key]) {
                acc[key] = {
                    block_number: data.block_number,
                    volume: data.volume,
                    tick: []
                };
            }
        
            acc[key].tick.push(entity.tick);
            return acc;
        }, {});
        return Object.values(result);
    }

reduce를 사용해서 기존에 acc안에 기존 값이 있다면 tick만 업데이트하고 기존 값이 없다면 최초 기록이므로 블록번호와 볼륨을 같이 넣어주면 된다.

이처럼 데이터 쌓는 지점을 정확히 체크하고 중복 업데이트를 막기 위해서 미리 블록번호당 데이터를 병합한 후에 넣어주고 데이터를 넣기 전에 데이터가 존재하는지 체크한다면 중복 적재 없이 db를 쌓을 수 있다.

배열 쪼개기

위와 같이 블록단위로 저장을 할 때 데이터가 많아서 배열 하나를 쪼개서 처리하고자 한다면 다음과 같은 함수를 사용할 수 있다.

    splitIntoChunks(array, chunkSize) {
        const results = [];
        while (array.length) {
            results.push(array.splice(0, chunkSize));
        }
        return results;
    }

 

 

728x90
반응형
Comments