Front-end Developer

0%

자바스크립트의 비동기성을 표현하는 기본 단위로써 콜백이 있는데, 콜백은 순차성과 믿음성이 결여되는 문제점이 있었다. 특히 콜백이 가진 제어의 역전은 실행 흐름을 서드 파티에 의존해야 하기 때문에 요청을 보내면 그 요청이 잘되기를 바라는 방법이 최선이었다. 이런 문제점을 보완하고 좀 더 나은 방법으로 비동기 처리를 할 수 있는 방법으로 프로미스가 등장하게 되는데, 제어의 되역전을 아이디어로 삼는다. 즉 실행 흐름을 서드 파티와 같은 다른 파트에 넘겨주지 않고도 개발자가 작업의 실행 결과와 다음 task에 대해 제어할 수 있는 것이다.

프로미스의 미랫값, 지금값, 나중값

미랫값시간 독립적인 값(Time Independent)이다. 시간의 흐름과 상관없이 미래에 성공 또는 실패로 귀결되는 값이다. 이 미랫값은 원래 내가 가지고 있던 값 자체와 교환된다. 미랫값이 성공이면 나는 언제가 되었든 반드시 원래 가지고 있던 값을 받게 되고, 실패이면 값을 받지 못한다. 그리고 이러한 미랫값을 다루는 방법이 콜백이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1️⃣
function add(getX, getY, cb) {
var x, y;

getX(function (xVal) {
x = xVal;
//둘 다 준비됐나?
if (y != undefined) {
cb(x + y); //더해서 보내
}
});
getY(function (yVal) {
y = yVal;
//둘 다 준비됐나?
if (x != undefined) {
cb(x + y); //더해서 보내
}
});
}

//fetchX()와 fetchY()는 동기/비동기 함수
add(fetchX, fetchY, function (sum) {
console.log(sum); //너무 쉽지?
});

위의 코드가 비동기 패턴이 아니라 일반 함수라고 가정해보자면, x,y의 값은 지금존재하는 구체적인 값이라는 가정 하에 연산이 진행될 것이다. 아래 예시를 통해 지금값에 대해 보충해 보겠다.

1
2
3
4
5
//2️⃣
var x,
y = 2;

console.log(x + y); //NaN <- x는 아직 세팅 전이다.

이 2️⃣번 예시는 비동기 패턴이 아닌 일반적인 계산 로직을 담고 있는 코드이다. 이 로직에서 x + y 가 연산되는 시점에서 코드는 x, y가 지금 존재하고 있는, 귀결(Resolved)된 값이라고 생각하고 연산이 이루어진다. 그렇기 때문에 x는 아직 할당 전이어서 실질적인 값은 undefined인데 지금 값이 존재한다고 가정되어 undefined + 2로 계산이 되어 NaN이라는 결과를 얻은 것이다.

만약 이 함수에서 x, y의 값을 연산할 때 지금 둘 중 하나라도 준비가 덜 됐으면 될 때까지 기다렸다가 나중에 값을 얻었을 때 연산을 진행한다고 가정해보자. 즉 코드에서 x, y를 지금 존재하는 귀결된 값이라고 가정하는 것이 아니라 미래에 실패 또는 성공할 값이라고 가정하고 연산을 진행하는 것이다. 그렇다면 결과값은 두 가지 값 모두 순조롭게 할당이 완료되어 성공적으로 정상적인 값을 출력하거나, 나중까지 기다렸는데도 정상적인 값이 할당되지 않아 undefined + 2로 계산된 것처럼 정상적인 결과를 출력하는데 실패하게 될 것이다. 그래서 x, y의 구체적인 연산 결과는 당장 예측할 수 없지만 그 값이 미래에 반드시 성공 또는 실패될 것이라는 사실을 예측할 수 있게 된다.

다시 돌아와서 1️⃣번 예제의 비동기 패턴을 살펴보자. 비동기 프로그래밍의 핵심은 지금에 해당하는 부분과 나중에 해당하는 부분 사이의 관계라고 하였다. 1️⃣번 예제에서 지금부터 나중까지 기다리는 최적의 방법으로 콜백 함수를 사용한다. 위에 설명했던 것처럼 지금 값이 귀결되어 있는지와 상관없이 일단 add() 함수를 호출하여 작성된 로직을 수행한다. add() 함수 내부의 x, y는 add()입장에서 실행되는 지금 시점에서 값이 준비되어 있는지 아닌지는 관심 밖이다. 여기서 x,y는 미랫값으로 취급된다. 따라서 그 값이 어떤 것인지와 상관없이 add()의 결과는 성공 또는 실패 일 것이라고 예측할 수 있게 된다.즉 x,y의 값이 지금 존재하는 값일 수도 있고, 나중에 값을 얻게 될 수도 있는데, 여기서는 지금나중의 어떤 때라도 결과론적으로 똑같이 일관적으로 동작할 수 있게 하기 위해서 이 두 가지 모두를 나중으로 만들어서 모든 작업을 나중에 얻게 될 성공 또는 실패의 값으로 비동기화시킨 것이다.

정리하자면 어떤 로직을 수행할 때, 값이 지금 존재하는지를 알기 여려운 경우, 해당 값을 지금 값이 존재하는지, 미래에 어느 시점에 존재하는 값으로 바뀌는지는 알 수 없지만 값 자체를 나중에 언젠가는 존재할 값이라는 가정 하에 로직을 수행하고, 나중에 그 값을 얻게 되면, 그 값은 구체적으로 로직의 연산 결과를 반환하는 값은 아니지만 반드시 성공 또는 실패한 값이다.

이 개념을 기억하며 프로미스 함수를 좀 더 자세히 살펴보자. 위 예제의 x + y를 프로미스 함수로 나타낸 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function add(xPromise, yPromise) {
return Promise.all([xPromise, yPromise]).then(function (values) {
return values[0] + values[1];
});
}

add(fetchX(), fetchY()).then(
function (sum) {
console.log(sum);
},
function (err) {
console.error(err);
}
);

Promise.all([])은 프로미스 배열을 인자로 받아서 프로미스가 모두 귀결될 때까지 기다렸다가 새 프로미스를 만들어 반환한다. 그리고 프로미스가 귀결되면 X와 Y 값을 받아 더하고, then에서 파라미터로 전달받은 values는 앞에서 귀결된 프로미스가 전달해 준 메시지 배열이다. 그리고 fetchX()와 fetchY()는 제각기 값을 가진 프로미스를 반환하는데, 지금 또는 나중에 준비된다. add()의 결과로 두 숫자의 합이 담긴 프로미스를 받으면 이제 반환된 프로미스가 귀결될 때까지 대기하기 위해 then()을 연쇄 호출한다.

위 예제에는 두 개의 프로미스 계층이 있다. 먼저 두 프로미스 fetchX()와 fetchY()를 호출하여 반환된 값은 add()에 전달된다. 프로미스 속의 원래 값은 지금 또는 나중에 준비되는데 그 시점과는 상관없이 각 프로미스가 같은 결과를 내게끔 정규화하고, 미랫값 X,Y는 시간 독립적으로 추론이 가능하다. 그리고 그 다음 프로미스 계층은 add()가 Promise.all을 거쳐 반환한 프로미스인데, then()을 호출하고 대기한다. add()가 끝나면 덧셈을 마친 미랫값이 콘솔에 출력된다.

이렇게 프로미스는 Fulfillment 또는 Rejection으로 귀결될 수 있다. 여기서 Fulfillment는 항상 프로그램이 귀결값을 결정짓고, Rejection은 프로그램 로직에 따라 직접 세팅되거나 런타임 예외에 의해 암시적으로 생겨난다.

그리고 프로미스 then() 함수는 Fulfillment 함수를 첫 번째 인자로, Rejection 함수를 두 번째 인자로 넘겨받는다. 그렇기 때문에 X나 Y의 조회 시 문제가 있거나 연산에 실패하면 add()가 반환하는 프로미스는 버려지고, then()의 두 번째 에러 처리 콜백이 이 프로미스에서 Rejection을 받는다.

요약해보자면 프로미스는 시간 의존적인 상태를 외부로부터 캡슐화하기 때문에 타이밍 또는 내부 결과값에 상관없이 예측 가능한 방향으로 조합할 수 있다. 또 프로미스는 일단 귀결되면 그 상태가 그대로 유지되는 불변값이다.

프로미스

프로미스 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값이다.

위에서 보았던 여러 예제를 통해 프로미스의 성격에 대해 알아보았다. 프로미스에서는 미랫값이라는 개념이 중요했다. 미랫값은 지금 당장은 값은 알 수 없고, 어느 시점에 그 값이 확정되는지 알 수 없지만 성공 또는 실패일 것으로 추론할 수 있는 값이었다. 프로미스는 이처럼 지금 당장 실행하는 시점에서 아직 알려지지 않을 수도 있는 값을 위한 대리자이고, 미랫값 개념을 콜백으로 다루는데, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있다. 그래서 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다. 다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'(프로미스)을 반환한다.

그래서 프로미스는 다음 중 하나의 상태를 가진다.

  • 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
  • 이행(Fulfillment): 연산이 성공적으로 완료됨.
  • 거부(Rejection): 연산이 실패함.

Promise가 생성된 시점에 대기 중인 프로미스 값은 성공적으로 이행(Fulfillment)이 되어서 위 예제에서 처럼 X + Y의 값을 얻을 수도 있고, 오류와 같은 이유로 인해 이행이 불가하여 거부(Rejection)될 수도 있다. 그리고 프로미스가 이행이나 거부로 귀결되면 then 메서드에 의해서 처리기들이 호출된다. 이 프로미스는 시간 의존적인 상태를 외부로부터 캡슐화하기 때문에 프로미스 자체는 시간 독립적이고, 따라서 언젠가 이행(Fulfillment)되거나 거부(Rejection)될 것을 예측하여 로직을 구성할 수 있다.

프로미스의 완료 이벤트

프로미스는 각각 미래값으로서 작동하지만 프로미스의 귀결은 비동기 작업의 여러 단계를 흐름 제어하기 위한 체계이다.

어떤 일을 하는 foo()라는 함수를 호출한다고 했을 때,

  • 전통적인 자바스크립트 사고 방식 :알림 자체를 하나의 이벤트로 보고 리스닝하고, foo()의 완료 이벤트를 리스닝함으로써 알림 요건을 재구성한다.
  • 콜백에 : foo()에서 넘겨준 콜백을 호출하면 성립된다.
  • 프로미스에서 : foo()에서 이벤트를 리스닝하고 있다가 알림을 받게 되면 다음으로 진행한다.

아래는 프로미스의 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
foo(x) {
//뭔가 시간이 제법 걸리는 일을 한다.
}

foo(42)

on(foo "완료") {
// 이제 다음 단계로 갈 수 있다.
}

on(foo "에러") {
//어랏, foo()에서 뭔가 잘못됐다.
}

foo()를 호출한 뒤에 완료, 에러 이벤트를 각각 리스닝하는 이벤트 리스너를 설정했다. foo()를 호출하면 foo()에서 이벤트를 받아 어떻게 처리하는지를 신경쓰지 않아도 되고, foo()의 결과는 완료 아니면 에러인 관심사의 분리가 된다.

이러한 코드는 사실상 일반적으로 콜백이 지향하는 코드와 정반대이다. 아래 예제를 보면 foo()에 콜백 함수를 넘겨주는 대신 foo()가 이벤트 구독기를 반환하고 여기에 콜백 함수를 넣는다. 이처럼 콜백 패턴을 뒤집는다는 것은 사실상 제어의 되역전으로 서드파티와 같은 다른 파트에 주었던 실행 흐름의 제어권을 호출부에 되돌려 둔 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(x) {
//뭔가 시간이 제법 걸리는 일을 시작한다.
//이벤트 구독기를 생성하여 반환한다.
return listener;
}

var evt = foo(42);

evt.on('completion', function () {
//이제 다음 단계로 갈 수 있다.
});

evt.on('failure', function (err) {
//어랏, foo()에서 뭔가 잘못됐다.
});

제어의 되역전으로 인해 이제 관심사를 분리할 수 있게 된다. 아래와 같은 코드가 있으면 bar()나 baz()는 foo()가 호출되어 처리되는 것에 신경 쓸 필요 없고, foo()도 마찬가지로 누군가 자신을 기다리고 있다는 사실을 몰라도 된다. 이렇게 분리된 관심사 간에 중재자 역할을 evt가 하는 것이고, 이것이 프로미스와 매우 유사하다. 프로미스식으로 아래 코드를 다시 작성한다면 foo()는 프로미스 인스턴스를 생성해서 반환하고, 이 프로미스를 bar()와 baz()에 전달할 것이다.

1
2
3
4
5
6
var evt = foo(42);

//bar()는 foo()의 완료 이벤트를 리스닝한다.
bar(evt);
//baz()는 foo()의 완료 이벤트를 리스닝한다.
baz(evt;)

References
Promise
[YOU DON’T KNOW JS] 카일 심슨 지음

자바스립트는 동기적이다. 동기적이라는 말은 자바스크립트가 싱글 스레드이기 때문에 한 번에 한 가지 task만 수행할 수 있다는 의미이다. 즉, 어떤 task1이 실행되면 이 task의 처리가 끝나기 전까지 다음 task인 task2는 실행되지 못하고, task1이 끝날 때까지 기다려야 한다. 그런데 브라우저 환경에서 task가 동기적으로 차리되면 예를 들어 서버에 요청한 후 어떤 결과값을 받아서 화면에 렌더링하는 task가 있다고 했을 때, 서버에서 요청이 언제 돌아올지 모르는 상태로 마냥 기다려야 하는데, 이 사이에 사용자가 보는 웹 브라우저 화면은 빈 화면인 상태 또는 task가 수행되기 이전의 상태에 머물러 있게 된다. 이런 문제 때문에 브라우저 환경에서 자바스크립트는 비동기적으로 실행된다. 정확히 말하자면 자바스크립트가 비동기적인 것이 아니라 브라우저가 비동기 처리를 이용하여 task를 마치 동시에 처리되는 것처럼 처리하고, 이러한 비동기 처리를 돕는 것은 이벤트 루프이다.

그렇다면 자바스크립트에서 말하는 비동기 프로그래밍의 개념에 대해 조금 더 상세히 살펴보겠다.

비동기 프로그래밍의 핵심은 지금에 해당하는 부분, 나중에 해당하는 부분 사이의 관계 또는 간극이다.

비동기성을 이해하기 위한 배경 지식

자바스크립트에서 프로그램은 여러 개의 chunk로 구성된다.

  1. 지금 실행 중인 프로그램 chunk
  2. 나중에 실행 할 프로그램 chunk

여기서 나중지금의 직후가 아니며, 지금 끝낼 수 없는 작업은 비동기적으로 처리되기 때문에 프로그램을 중단하지 않는다. AJAX를 예로 들자면 이 함수는 비동기적으로 어떠한 task를 지금 요청하고 나중에 결과를 받고, 지금부터 나중까지 기다리는 최적의 방법은 콜백 함수를 이용하는 것이다.

이벤트 루프

앞서 말했지만 자바스크립트는 동기적이라서 주어진 요청(task)가 주어지면 주어진대로 순서대로 처리할 뿐이다. 자바스크립트 엔진은 웹 브라우저와 같은 호스팅 환경에서 실행되는데, 이벤트 루프가 여러 프로그램 chunk를 시간에 따라 매 순간 한 번씩 엔진을 실행시키도록 한다. 즉 자바스크립트 엔진이 동기적으로 코드를 실행할 때 AJAX, 콜백함수와 같이 비동기 처리 함수가 있다면 브라우저 환경이 이벤트 루프를 통해 실행을 스케줄링 하는 것이다. 이 스케줄링이 비동기적으로 처리되기 때문에 자바스크립트 자체는 동기적이지만 브라우저 환경에서의 실행 스케줄링은 비동기적이라고 할 수 있다. 구현방식을 단순화해서 구현하면 다음과 같다.

while문의 무한 루프에서 매 순회를 틱이라 하는데, 한 번 틱이 발생할 때, 큐에 있는 이벤트(콜백 함수)를 선입 선출하여 실행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//eventLoop는 큐(선입, 선출) 역할을 하는 배열
var eventLoop = [];
var event;

//'무한'실행
while (true) {
//'틱' 발생 (틱: 이벤트 루프를 한 차례 순회하는 것)
if (eventLoop.length > 0) {
//큐에 있는 다음 이벤트 조회
event = eventLoop.shift();
//이제 다음 이벤트 실행
try {
event();
} catch (err) {
reportError(err);
}
}
}

싱글 스레드 vs 병렬 스레드

자바스크립트의 비동기 처리에 대해서 설명할 때 종종 병렬과 혼용하여 사용하는데, 둘은 완전히 다른 개념이다.

  • 비동기 : 지금나중 사이의 간극에 관함
  • 병렬: 동시에 일어나는 일에 관함

자바스크립트는 하나의 프로그램에서 여러 스레드를 처리하는 병렬 시스템이 아니라 절대로 스레드 간에 데이터를 공유하지 않는 단일 스레드 환경이다. 그리고 단일 스레드이기 때문에 완전-실행(Run-to-Completion)된다. 즉 함수가 2개 있다면 첫 번째 함수의 전체 코드가 다 실행된 후, 다음 함수가 실행된다. 그리고 두 함수가 있다면 이 함수는 순서에 따른 비결정성을 가진다. 이를 경합 조건(Race Condition)이라 한다.

동시성

복수의 프로세스가 같은 시간 동안 동시에 실행된다. 앞서 말했듯이 자바스크립트는 병렬 스레드가 아니므로 여기서 말하는 동시성은 각 프로세스 작업(개별 프로세스의 스레드)이 병렬로 처리되는지와 관계된 것이 아닌, 프로세스 수준(작업 수준)의 병행성을 말한다. 단 프로세스 1과 프로세스 2가 있다면 이들은 동시에 실행되지만 이들을 구성하는 이벤트들은 이벤트 루프 큐에서 차례대로 실행된다.

상호작용 vs 비상호 작용

1
2
3
4
5
6
7
8
9
10
11
12
13
var res = {};

function foo(results) {
res.foo = results;
}

function bar(results) {
res.bar = results;
}

//ajax()는 라이브러리에 있는 임의의 AJAX 함수
ajax('http://some.url.1', foo);
ajax('http://some.url.2', bar);

위 코드에서 복수의 프로세스 foo(), bar()가 동시에 실행될 때, 누가 먼저 실행될 지는 알 수 없지만 이들 프로세스 사이에 연관된 작업이 없기 때문에 프로세스간 상호작용이 일어나지 않는다. 이런 경우에 실행 순서가 문제되지 않는다. 즉 경합 조건이 문제시 되지 않는다. 하지만 상호 작용하는 상황이라면 이야기는 달라진다.

1
2
3
4
5
6
7
8
9
10
11
var res = [];

function response(data) {
res.push(data);
}

//ajax()는 라이브러리에 있는 임의의 AJAX 함수
//1️⃣
ajax('http://some.url.1', response);
//2️⃣
ajax('http://some.url.2', response);

위의 코드는 상호 작용이 발생하는 프로세스이다. 1️⃣과 2️⃣의 프로세스는 모두 AJAX 응답에 대한 처리를 하는 response() 함수를 호출하기 때문에 선발 순으로 처리된다. 이 코드가 프로그램 개발자가 ‘http://some.url.1'의 결과를 res[0]에, ‘http://some.url.2',의 결과를 res[1]에 넣고자 의도한 코드라고 했을 때, 의도한 대로 동작할 수 있지만 어느쪽 URL의 응답이 먼저 도착할지 보장되지 않기 때문에 만약 예측한 대로 응답이 도착하지 않을 경우 결과가 뒤집힐 수 있다. 이런 경합 조건을 해결하려면 상호 작용의 순서를 잘 조정해야 한다. 특히 동시 프로세스들이 스코프나 DOM을 통해 간접적으로 상호작용하기 때문에 순서 조정이 아주 중요하다. DOM을 조작하는 코드의 경우 순서 조정이 제대로 이루어지지 않으면 처리가 덜 된 DOM 요소를 화면에 보여주게 될 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var res = [];

function response(data) {
if (data.url == 'http://some.url.1') {
res[0] = data;
} else if (data.url == 'http://some.url.2') {
res[1] = data;
}
}

//ajax()는 라이브러리에 있는 임의의 AJAX 함수
//1️⃣
ajax('http://some.url.1', response);
//2️⃣
ajax('http://some.url.2', response);

위와 같이 작성하게 되면 어느 쪽에 응답이 먼저 오더라도 data.url을 보고 해당하는 결과를 res[0] 또는 res[1]에 담을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var a, b;

function foo(x) {
a = x * 2;
if (a && b) {
baz();
}
}

function bar(y) {
b = y * 2;
if (a && b) {
baz();
}
}

function baz() {
console.log(a + b);
}

//ajax()는 라이브러리에 있는 임의의 AJAX 함수
//1️⃣
ajax('http://some.url.1', foo);
//2️⃣
ajax('http://some.url.2', bar);

위의 코드에서 if (a && b) { baz() }의 조건은 관문(Gate)라 부른다. a와 b 둘 중 누가 먼저 도착할지 알 수 없지만 반드시 둘 다 도착한 다음에 baz()함수가 호출이 되는, 즉 관문이 열리기 때문이다. 이 코드가 존재하지 않다면 baz() 함수가 초기에는 a,b가 undefined인 상태에서도 호출이 되어 제대로 작동하지 않는다. 이와 다르게 둘 다 도착했을 때가 아닌, 둘 중 하나만 도착했을 때 상호 작용하도록 하는 코드도 있다. 이는 관문(Gate)보다 걸쇠(Latch)로 불리고, 선착순 한 명만 이기는 형태이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var a;

function foo(x) {
if (!a) {
a = x * 2;
baz();
}
}

function for(y) {
if (!a) {
a = x / 2;
baz();
}
}

function baz() {
console.log(a);
}

//ajax()는 라이브러리에 있는 임의의 AJAX 함수
//1️⃣
ajax('http://some.url.1', foo);
//2️⃣
ajax('http://some.url.2', bar);

위의 코드에서 if (!a) {}와 같은 코드를 걸쇠(Latch)라 부른다. foo()나 bar() 둘 중 첫 번째 실행된 함수만이 이 조건을 통과하고 늦게 실행된 함수 호출은 무시되기 때문이다.


콜백

콜백은 백그라운드에서 코드 실행을 시작할 함수를 호출할 때 인수로 지정된 함수이다. 백그라운드 코드 실행이 끝나면 콜백 함수를 호출해서 작업이 완료되었음을 아리거나 다음 작업을 실행하게 할 수 있다.

1
2
3
4
5
6
7
btn.addEventListener('click', () => {
alert('You clicked me!');

let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});

위의 함수에서 콜백은 addEventListener()의 click 옆의 두 번째 매개변수이다. 이벤트가 실행 될 때 이 콜백 함수가 호출된다. callback 함수를 다른 함수의 인수로 전달할 때, 함수의 참조를 인수로 전달할 뿐이지 즉시 실행되지 않고, 함수의 body에서 “called back”된다. 즉 정의된 함수는 때가 되면 callback 함수를 실행하는 역할을 한다. 또한 콜백은 자바스크립트에서 비동기성을 표현하고 관리하는 가장 일반적인 기법이자 가장 기본적인 비동기 패턴이다.

콜백의 실체

연속성

비동기 코드 작성의 어려움은 콜백 함수의 연속성과 인간 두뇌의 연속성의 개념이 다르기 때문이다.

1
2
3
4
5
6
//A
ajax("...", function(..){
//c
});

//B

위와 같은 코드가 있다고 했을 때, 지금에 해당하는 전반부 코드(A,B)가 실행되면 비결정적(indeterminate) 시간 동안 중지되고 언젠가 AJAX 호출이 끝날 때 중지되기 이전 위치로 다시 돌아와서 나머지 후반부(C)프로그램이 이어진다. 즉 콜백 함수는 프로그램의 연속성을 감싼(캡슐화)한 장치이다. 그런데 이러한 코드는 순차적으로 task를 처리하는 두뇌의 처리방식과 다른 순서를 같기 때문에 추론이 어렵다. 즉 위와 같은 코드를 보면 두뇌는 A -> C -> B의 순서대로 작성된 코드를 읽어내려가며 추론하려 할 것이다. 하지만 실질적인 코드는 A -> B -> C의 순서대로 실행하기 때문에 이러한 괴리감 때문에 비동기 처리를 이해하기 어렵게 만든다.

콜백지옥

비동기 처리를 위해 콜백을 중첩 또는 연속해서 사용되면 아래와 같은 콜백 지옥이 발생한다.

1
2
3
4
5
6
7
8
9
10
11
listen('click', function handler(evt) {
setTimeout(function request() {
ajax('http://some.url.1', function response(text) {
if (text == 'hello') {
handler();
} else if (text == 'world') {
request();
}
});
}, 500);
});

이러한 콜백 지옥으로 거론되는 첫 번째 문제는 콜백을 연속해서 쓰다보니 점점 들여쓰기가 되면서 가독성이 떨어져서 유지 보수를 어렵게 하는 것이다. 하지만 들여쓰기로 인한 가독성 저하보다 코드를 너무 복잡하게 만든다는 점이 더 큰 문제이다. 중접된 콜백으로 인한 가독성 저하를 막기 위해 콜백 작성시 조건을 작성해서 좀 더 가독성을 높일 수 있는데, 이러한 하드 코딩은 사실 코드를 더 복잡하게 만들고, 코드가 복잡해지는 것이야말로 유지보수를 어렵게 만든다. 즉 콜백의 단계별 맞닥뜨릴 수 있는 경우의 수를 분기처리해 줄 수 있는데, 분기처리를 해야하는 가능한 경우의 수를 나열하다보면 코드가 방대해지고, 작성된 구문이 모든 경우의 수를 커버하라는 보장도 없다.

믿음성 문제

1
2
3
4
5
6
//A
ajax("...", function(..){
//c
});

//B

위와 같은 함수는 제어권을 주고받는 행위가 발생한다. A,B가 자바스크립트 메인 프로그램의 제어를 받으며 지금 실행된다면 C는 다른 프로그램(ajax() 함수)의 제어하에 나중에 실행된다. 이 제어권 교환이야말로 콜백 중심적 설계의 가장 큰 문제점이다. 위 코드에서 쓰인 ajax()처럼 콜백을 넘겨주는 코드는 개발자가 직접 제어할 수 있는 함수가 아니라 서드 파티가 제공한 유틸리티인 경우가 대부분이다. 이렇게 내가 작성하는 프로그램인데도 실행 흐름은 서드 파티에 의존해야 하는 상황을 제어의 역전이라 한다. 그래서 비동기 콜백 함수를 작성한 경우, 콜백 자체에 대해 개발자가 직접 제어할 수 없기 때문에, 콜백 호출 시 오류가 날 수 있는 상황에 대한 여러가지 보완 로직을 구현하기 마련이다. 예를 들어 콜백을 너무 일찍 부르거나 너무 늦게 부른다거나, 너무 많이 부른다거나 하는 등의 잘못 될 가능성이 있는 상황에 대한 보완 로직을 작성해 두는 것이다. 하지만 보완 로직을 작성했다고 해도 서드 파티의 처리에서 무언가 문제가 생기면 완전히 잘못 틀어질 수도 있다. 그래서 이런 믿음성의 문제를 해결하기 위해 몇 가지 콜백 체계가 존재한다.

  • 분할 콜백
  • 에러 우선 스타일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//분할 콜백 - 한쪽은 성공, 한쪽은 실패
function success(data) {
console.log(data);
}
function failure(err) {
console.error(err);
}

ajax('http://some.url.1', succes, failure);

//에러 우선 - 성공 시 빈/falsy 객체, 실패 시 truthy/에러 객체로 세팅됨.
function response(err, data) {
//에러인가?
if (err) {
console.error(err);
}
//아니면 성공한 것
else {
console.log(data);
}
}

ajax('http://some.url.1', response);

언뜻 보면 믿음성에 대한 문제가 해결된 것 같지만 원하지 않는 반복적인 호출을 방지하거나 걸러내는 콜백 기능은 전혀 없다. 오히려 성공/에러를 동시에 받거나 전혀 받지 못하는 상황에 대해서도 고려해야 하고, 이렇게 표준적인 형태로 작성되었어도 재사용이 불가하거나 장황한 관용 코드라서 콜백을 쓸 때마다 매번 새로 타이핑을 해주어야 할 수 있다.

정리하자면 콜백이 자바스크립트의 비동기성을 표현하는 기본 단위로써 충분히 그 역할을 다 해왔지만, 점점 진화하는 비동기 프로그래밍 환경에 대응하기에는 충분하지 않다.
첫 번째 이유는 연속성의 문제로 사람의 두뇌는 순차적이고, 단일-스레드 방식으로 계획하는데 익숙하지만 콜백은 비동기 흐름을 비선형적, 비순차적으로 나타내기 때문에 그 괴리감으로 인해 구현된 코드를 사람이 이해하기가 쉽지 않다. 이렇게 추론하기 어려운 코드는 악성 버그를 품을 가능성을 내재한 코드가 된다. 두 번째, 콜백이 프로그램을 진행하기 위해 제어권을 다른 파트(e.g. 서드파티 유틸리티)로 넘겨줘야 하는데, 이렇게 제어권이 넘어가면 믿음성의 문제에 봉착해서 콜백이 잘못될 가능성에 대해 구구절절한 보완 로직을 작성하게 된다. 이렇게 작성하면 믿음성의 문제는 해결할 수 있을지라도 거칠과 유지보수가 어려운 방대하고 복잡한 코드가 되고, 실제로 100% 믿음성의 문제를 해결했다고 보기도 어렵다. 모든 케이스에 대해 보완 로직을 작성했다고 해도, 제어권이 넘어간 상태에서 발생한 일까지 대응할 수는 없기 때문이다.

결국 이렇게 제기된 문제들을 해결하려면 콜백을 능가하는 조금 더 나은 해결법이 필요하고, 그런 배경 아래 등장한 것이 프로미스이다.


References
callback
[YOU DON’T KNOW JS] 카일 심슨 지음

22. 냉정하게 한계점 바라보기

블록체인 시스템이 가진 기술적 제약사항

  1. 개인정보 보호의 부재
  2. 보안 모델
  3. 제한적 확장성
  4. 고비용
  5. 숨겨진 중앙 통제
  6. 유연성의 부재
  7. 임계 크기

1. 개인정보 보호의 부재

블록체인에서 발생하는 모든 트랜잭션에 대한 정보는 상세히 기록되고 누구나 읽을 수 있다. 이렇게 정보는 보호되지 않지만 개인정보는 철저히 보호된다. 하지만 개인정보가 누구의 것인지 상세하게 알 수 없기 때문에 개인정보 보호의 부재라기보다 개인정보의 부재라고 볼 수 있고, 이런 것을 중요시하는 응용 분야에서는 이로부터 비롯되는 제약사항이 있을 수 있다.

2. 보안 모델

블록체인에서 트랜잭션 승인을 위해 비대칭 암호화 기법을 사용하는데 이는 가장 강력한 기법이기 때문에 강력한 보안이 가능하다. 하지만 블록체인 사용자가 개인 키를 분실하거나 개인 키가 타인의 손으로 넘어간 경우 계정 주인을 보호할 수 있는 추가 안전 장치가 없다. 비트코인은 개인 키의 해킹 등에 대비하는 몇 가지 안전장치를 제시하고 있다. 하지만 기본적으로 개인 키를 분실한 순간 보안은 무너졌다고 볼 수 있다.

3. 제한적 확장성

블록체인은 누구나 새로운 트랜잭션 데이터를 추가할 수 있도록 개방되어 있는 한편, 트랜잭션 데이터가 조작되거나 위조되지 않도록 보장해야 한다. 이를 위해 새로운 블록이 추가될 때마다 해시 퍼즐을 풀어야 하는데, 이 퍼즐을 풀 때 상당히 많은 계산량이 소모되도록 설계되어 있다. 이는 트랜잭션의 조작을 막는 것에 좋은 방법이지만 빠른 처리속도와 높은 확장성, 대용량 처리가 필요한 응용분야에서는 오히려 걸림돌이될 수 있다.

4. 고비용

제한된 확장성과 관련 있는데, 해시 퍼즐을 풀 때 많은 계산량을 소모한다는 것은 그만큼 많은 비용(필요 계산 사이클, 물리적 시간, 소모 전력량 등)을 유발한다는 것이고, 전체 비용은 해시 퍼즐의 난이도에 달려있다.

5. 숨겨진 중앙 통제

해시 퍼즐을 해결하기 위해 특화된 하드웨어에 투자하여 더 많은 보상을 받아서 이윤을 창출하는 사람들이 있는데, 이와 반대로 특화된 하드웨어가 없는 사람은 전자에 비해 밀릴 수 밖에 없어 시스템을 떠나게 된다. 따라서 특화된 하드웨어로 강력한 계산 능력을 갖춘 소수만 남아 시스템을 장악하는 상황을 초래할 수 있고, 이 소수가 시스템의 무결성을 유지하는 책임을 나눠가지게 된다. 이렇게 되면 사실상 보이지 않는 중앙 통제 집단을 형성하여 피어 그룹이 집단적으로 무결성을 유지하도록 하는 분산 시스템의 근본을 위협한다. 즉 기술적으로는 분산 시스템이지만 시스템의 무결성은 소수의 개체에 의해서만 유지되는 것이다.

6. 유연성의 부재

블록체인을 일단 가동하고 나면 변경, 업그레이드와 관련된 체계적 절차가 따로 없다. 이런 불변성은 블록체인 개발자가 체인 프로토콜의 버그를 고치거나 수정하기 어렵게 하고, 전체 블록체인-기술-모음을 유연성이 떨어지게 만든다.

7. 임계 크기

트랜잭션 데이터를 조작으로부터 보호하여 신뢰성을 확보하는 장치는 대다수의 시스템 계산 자원이 정직한 노드에 의해 통제된다는 가정에 기반한다. 그래서 모든 블록체인이 엄청난 계산력을 갖춘 공격자로부터 시스템을 보호하기 위해 최소한의 정직한 노드가 필요하다.

비기술적 문제 2가지

  1. 법적 수용성 부재
  2. 사용자 수용성 부재

1. 법적 수용성 부재

블록체인으로 분산 합의를 통해 소유권 이전 및 관리를 하는 것은 법적 효력에 대한 해석 문제를 야기하고, 이는 법적 제도 장치의 결여를 의미한다.

2. 사용자 수용성 부재

법적 수용성 부재와 같은 요소가 사용자들의 블록체인에 대한 불확실성을 가지게 하고, 사용 결정에 대한 관심을 감소시킨다. 또 지식과 교육의 부재로 기본 작동 원리에 대해 모르기 때문에 블록체인에 대해 신뢰하기도 어렵다.

어떻게 극복할 것인가?

기술적 문제

➡️ 모든 구성요소와 기술 수준을 조정한다. 블록체인의 기술적 제약사항을 극복하는 데 있어 주요 걸림돌은 기술을 개선하는 것과 기본 기술을 변경하는 것에 대한 구분이다.

비기술적 문제

➡️ 교육과 법제도의 개선이 적절한 수단이 될 수 있다.


23. 다시 태어난 블록체인

블록체인의 서로 상충되는 목표

  1. 투명성 vs 개인정보 보호
  2. 보안 vs 속도

1. 투명성 vs 개인정보 보호

블록체인이 소유권을 검증하는 핵심 개념이 개방성과 투명성이다. 개인정보 보호는 트랜잭션에 연계된 계좌번호나 이체금액 등의 세부 사항이 공개되지 않도록 하는 것인데, 소유권 명확화를 위해 모든 트랜잭션이 투명하게 관리되는 것과 서로 충돌한다.

2. 보안 vs 속도

블록체인은 트랜잭션 데이터를 보호하기 위해 해시 퍼즐을 풀 때 많은 계산량을 요구로 하는데, 이런 성질 때문에 속도와 확장성에 정면으로 반한다.

갈등의 근본 원인은 읽기와 쓰기 권한에 있다

기술적 제약사항 갈등 요소 기본 기능
개인정보 보호 부재 투명성 vs 개인정보 보호 트랜잭션 데이터 이력 읽기
확장성 부재 보안 vs 속도 트랜잭션 데이터 쓰기

갈등 해결, 4가지 버전의 블록체인을 등장시키다

갈등은 절충점을 찾거나 한쪽의 희생을 감수하고 다른 한쪽을 선택하면 해결된다.

투명성과 개인정보 중 선택

데이터를 읽을 권한을 누구에게 줄 것인가를 정하는 문제이다.

  • 공개 블록체인: 모든 사용자와 노드에게 트랜잭션 데이터를 읽을 수 있는 권한을 부여한다. (모두에게 읽기 권한 부여)
  • 비밀 블록체인: 사전에 선택된 사용자와 노드에게만 트랜잭션 데이터를 읽을 수 있는 권한을 부여한다. (제한된 사용자와 노드 그룹에게만 읽기 권한 부여)

보안과 속도 중 선택

  • 무승인형 블록체인: 모두에게 쓰기 권한을 부여한다. 즉 누구나 트랜잭션을 검증하고 새 블록을 추가할 수 있다.
  • 승인형 블록체인: 온-보딩 프로세스를 거쳐 신뢰성이 확인된 소수의 노드와 사용자 그룹에게만 쓰기 권한을 허용한다. 쓰기 권한을 받은 소수마 트랜잭션의 검증과 분산 동의에 참가할 수 있다.

블록체인의 네 가지 버전, 어떤 점이 다를까?

읽기와 트랜잭션 생성 권한
모두 허용 허가된 사용자만 허용
쓰기권한 모두 허용 공개&무승인 비밀&무승인
허가된 사용자만 허용 공개&승인 비밀&승인

위의 조합괸 버전들은 블록체인의 다음 세 가지 측면에 영향을 미친다.

  1. P2P 아키텍처
  2. 분산 속성
  3. 목적

1. P2P 아키텍처

시스템에 참여한 모든 컴퓨터에게 동등한 권리와 역할을 부여해야 하는데, 위에서 설명된 버전 중 일부는 시스템을 구성하는 노드별로 읽기와 쓰기 권한에 차별을 둔다. 따라서 동등한 권리와 역할을 부여한다는 P2P 시스템의 중요한 특성에 위배된다.

2. 분산 속성

분산 시스템에서 중앙 통제나 조정장치가 없어야 한다. 그러나 위에서 설명된 버전 중 일부는 읽기와 쓰기 권한을 사전에 승인한 노드나 사용자 그룹으로 제한하고 있다. 그리고 이런 승인과 권한에 관한 규칙이 순수 분산 시스템에 의해 유지되고 관리되지 않는다면 중앙 통제 요소가 있다는 것이어서 분산 속성에 위배된다. 또 숨은 중앙 통제 요소가 있거나 내부적으로 분산 시스템을 활용하지만 모든 노드에게 접근제한이 있는 중앙 통제 시스템을 볼 수 있는데 이 경우 모두 분산과 중앙 통제 요소로 구성된 혼합시스템에 가깝다.

3. 목적

읽기와 쓰기 권한에 제한을 두는 것은 분산 P2P 시스템의 기본 성질을 변형시킬 뿐 아니라 노드의 신뢰성도 변화시킨다. 하지만 안정성과 신뢰성을 알 수 있는 특정된 개수의 노드로 구성된 환경에서도 블록체인의 역할이 존재한다.

  • 기술적 결함이나 컴퓨터 고장으로 작동하는 노드의 개수가 변경될 수 있다.
  • 모든 분산 시스템은 네트워크 장애로 인해 개별 메시지단위의 통신이 안정적이지 못하다.
  • 노드의 신뢰성을 100% 수준으로 보장할 수 없다.

블록채인의 목적 다시 정의하기

노드별로 읽기와 쓰기 권한에 제약을 두면 블록체인의 주요 측면과 상충하는데 제약이 가장 많은 비밀-승인형 블록체인에서도 여전히 무결성 유지에 아주 중요하다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

19. 컴퓨터들도 라인을 잘 타야 살아남는다

시스템의 개별 노드가 유지하고 있는 트랜잭션 이력이 서로 달라 충돌이 생길때 문제를 어떻게 해결할지 알아본다.

목표

네트워크 내 모든 노드가 명확한 단일 트랜잭션 이력만 유지하도록 하여 어느 노드가 소유권 확인 요청을 처리하든 항상 동일한 결과가 산출되도록 한다.

해결해야 할 과제

블록체인-알고리즘에는 중앙 통제 장치가 없기 때문에 개별 노드의 수신함에 도착한 새 블록이 그 노드가 무슨 일을 해야 할지 알려준다. 각 노드는 어떤 메시지가 도착했냐에 따라 작업을 전환하는데 같은 시간에 모든 노드의 작업 전환이 동시에 발생하는 것이 아니므로 개별 노드의 작업 상태에 중첩이 생길 수 있다. 이처럼 메시지 전달 과정에서 문제가 발생했을 때 명확한 단일 트랜잭션 이력을 식별하는 방법을 찾아야 한다.

아이디어

모든 노드가 단일 버전의 트랜잭션 이력을 선택한다.

집단적 의사결정 문제에서 동의나 합의에 이르는 과정을 분산 합의라 부르는데 집단적 의사결정 상황은 다음의 네 가지를 충족한다.

  • 모든 노드는 네트워크, 블록체인-데이터-구조의 개별 복사본을 유지하는 노드들, 노드들의 행동을 통제하는 블록체인-알고리즘으로 구성된 동일한 환경에서 작동한다.
  • 집단적 의사결정 문제는 단일 트랜잭션 이력을 선택하는 것이다.
  • 모든 노드는 수입을 최대화하기 위해 블록체인-데이터-구조에 유효한 새 블록을 추가하고 보상을 받으려 노력한다.
  • 모든 노드는 목표 달성을 위해 자신이 생성한 새 블록을 피어들에게 보내 검사받고 인정받으려 한다. 그 결과 각 노드는 집단적으로 유지되는 블록체인-데이터-구조 환경에 개별적인 흔적을 남기게 된다.
  • 환경 변화에 기반한 동일한 기준을 사용해 의사결정 문제를 평가한다. 여기서 기준은 각 트랜잭션 이력을 생성하기 위해 사용한 계산 노력의 누적 총합이고, 둘 이상의 상충된 트랜잭션에서 조작된 데이터를 분별하기 위한 기준으로 활용된다.

작동원리

계산 노력을 판단하는 기준

가장-긴-체인 기준

새 블록을 만들어 기존 체인을 확장하려고 할 때, 어떤 노드는 해시 퍼즐을 해결하여 새 블록을 피어들에게 전송하여 블록-체인-데이터 구조를 형성할 것이다. 그리고 이렇게 가장 최근 추가된 새 블록을 이전 노드로 만드는 또다른 새 블록을 만들기 위해 노력하게 될텐데 이는 곧 세 개의 블록으로 구성된 오직 한 가지 버전의 블록체인-데이터-구조만 존재한다는 것이다. 단 네트워크 상의 문제로 인해 새 노드를 전달받지 못하는 상황에서 전달받지 못한 노드 중 하나가 해시 퍼즐을 해결하여 다른 피어들에게 해시값을 전송하게 되면 대다수의 피어들이 2개의 블록을 수신하게 된다. 이런 경우 상단에 두 개의 가지가 뻗어있는 블록체인-데이터-구조를 유지하게 되는데, 체인의 길이가 같다면 각 노드는 어느 쪽 가지를 확장할 지 임의로 정한다. 그런데 이때도 앞서 말한 두 개의 블록을 수신하게 되는 상황이 발생할 수 있다. 결국 가장-긴-체인 기준을 적용하면 대다수의 노드 그리고 궁극적으로 시스템의 모든 노드가 소유권의 명확화와 관련된 요청에 대해 이 체인을 사용하게 되고, 블록체인-데이터-구조는 일직선으로 이어진 체인이 아니라 트리나 원주형 선인장처럼 생긴 구조를 가지게 된다.

가장-무거운-체인 기준

가장 많은 블록을 가지고 있는 경로가 가장 많은 계산 노력을 소모한 것이라는 가정 하에 가장-긴-체인 기준에 대해 살펴보았다. 그런데 난이도가 블록마다 다를 때 가장 긴 경로가 반드시 가장 많은 계산 노력을 소모했다는 보장이 없다. 각 경로별로 실제로 사용된 계산 노력을 측정하려면 각 블록 헤더의 난이도를 모두 더하면 된다. 이렇게 계산해보면 가장-긴-체인이 반드시 가장 많은 계산 노력을 소모한 것은 아니라는 사실을 알 수 있고, 이 때문에 동적으로 난이도를 결정하는 블록체인은 가장-무거운-체인 기준으로 가장 무거운 체인을 가진 트랜잭션 데이터 이력을 선택하다.

선택받지 못한 체인은 어떻게 될까?

특정 체인을 선택하면 다음의 결과가 나온다.

고아 블록
보상 회수
소유권 명확화
트랜잭션 재처리
공통 몸통의 성장
궁극적 일관성
조작에 대한 견고성

1. 고아 블록

권위 체인이 선정되면, 트리-모양 데이터 구조 내 권위 체인 경로에 속하지 않은 블록은 모두 버려진다.

2. 보상 회수

고아 블록은 권위 체인에 기여하지 않으므로 소유권 명확화에 아무 쓸모가 없고, 고아 블록을 생성하고 제출해서 보상을 받았던 노드들로부터 보상을 회수한다.

3. 소유권 명확화

권위 체인에 속한 트랜잭션들만 실제로 발생한 것으로 인정받아 소유권 명확화에 사용된다.

4. 트랜잭션 제자리

고아 블록에 소속돼 버려진 트랜잭션 데이터는 노드의 수신함에 넣어져 재처리된 후 블록체인-데이터-구조에 다시 추가되어 선택된 트랜잭션 이력의 일부가 될 기회를 한 번 더 부여받는다.

5. 공통 몸통의 성장

트랜잭션 이력의 상충되는 버전은 덜 모호한 공통 몸통으로부터 뻗어나온 것이므로 깊이 들여다볼수록 해당 블록이 가장 긴 체인의 일부인지 아닌지가 명확해진다.

6. 궁극적 일관성

가장-긴-체인-기준이 모호할 때 추가된 다음 블록에 의해 어느 것이 가장 긴 체인의 일부가 될지 결정하는데, 이 다음 블록이 블록체인-데이터-구조의 어디에 붙어서 가지를 확장할지는 노드가 알아서 결정한다. 트리-모양 블록체인-데이터-구조는 해시 퍼즐의 속도 경쟁과 네트워크를 통한 메시지 전달의 임의 변동성에 영향을 받으려 무작위로 성장하는데 선택된 가지들은 몸통에 그대로 붙어있다. 따라서 권위 체인의 정상이나 그 근처 블록들은 새 블록이 랜덤으로 도착하는 성질에 영향을 많이 받는다. 시간이 지나 더 많은 블록이 추가될수록 권위 체인에 포함될 가능성이 더 높아지는 현상을 궁긍적 일관성이라 부른다.

7. 조작에 대한 견고성

트리-모양의 블록체인-데이터-구조 중 가장 계산 노력이 가장 많이 들어간 경로가 트랜잭션 이력의 권위 있는 버전이다. 블록체인-데이터-구조 내부의 특정 블록에서 시작하는 새로운 권위 경로를 만들려면 대다수가 유지하고 있는 경로를 따라잡고 앞질러야 하고, 이 점이 블록체인이 가진 견고성의 기초가 된다. 즉 공격자가 내부 블록을 조작하려한다면 작업 증명을 다시 하고, 모든 후속 블록의 해시 퍼즐을 다시 풀어서 정직한 노드들이 유지하고 있는 경로를 따라잡아야 하는데 사실상 불가하고, 결론적으로 시스템이 유지하는 트랜잭션 이력은 조작으로부터 견고하다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

16. 블록체인이 데이터를 보호하는 방법

목표

블록체인이 유지하는 전체 트랜잭션 이력을 불변성 데이터로 만들어 위조나 조작이 없게 한다.

해결해야 할 과제

누구에게나 개방된 순수 분산 P2P 시스템을 개방한 채 조작이나 위조의 위협으로부터 트랜잭션 데이터를 보호한다.

아이디어

처음부터 아무도 이력을 조작하지 못하게 막아놓는다.

불변성은 읽기 전용의 다른 이름

불변성 데이터는 한번 생성되면 절대로 변경될 수 없고, 이러한 데이터는 읽기 전용 데이터라고도 한다. 데이터를 변경과 조작으로부터 보호하는 효과적인 방법이다.

작동원리

트랜잭션 이력을 변경하는 일이 감당 불가라서 누구라도 단념하게 만드는 방법으로 트랜잭션 이력을 불변성 데이터로 만든다.

트랜잭션 이력을 불변성 데이터로 만드는 데 필요한 세 가지

  • 트랜잭션 이력을 저장할 때 아무리 사소한 조작이라도 바로 드러날 수 있게 한다.
    • 아무리 사소한 변경이라도 데이터를 가리키는 해시 참조를 무효화 시켜서 몰래 조작이 불가하다.
  • 변경된 데이터를 끼워넣으려면 엄청난 양을 다시 작성하도록 강제한다.
    • 변경하려면 변경된 부분부터 헤드까지 전체 체인을 완전히 변경하거나 내버려둬야 한다.
  • 트랜잭션 이력에 데이터를 추가, 변경, 재작성하는 작업에 엄청난 계산량이 필요하도록 한다.
    • 블록체인-기술-모음은 블록체인-데이터-구조의 모든 블록에 대해 작성, 재작성, 추가 작업에 엄청난 계산 비용이 필요하게끔 만든다.

작동원리: 세부 사항 살펴보기

블록체인-데이터-구조의 새 블록 추가에는 많은 계산량이 들지 않지만 이것을 불변성으로 만들려면 새 목록을 추가하는 것이 많은 계산량을 필요로 하게끔 해야한다. 이를 위해서 아래와 같은 사항을 고려한다.

  • 필수 데이터
  • 새 블록 헤더를 생성하는 프로세스
  • 블록 헤더를 검증하는 규칙

필수 데이터

블록체인-데이터-구조의 모든 블록 헤더는 최소한 다음의 데이터를 가진다.

  • 트랜잭션 데이터를 담고 있는 머클 트리의 루트
  • 이전 블록 헤더를 가리키는 해시 참조
  • 해시 퍼즐의 난이도
  • 해시 퍼즐 풀이를 시작한 시각
  • 해시 퍼즐을 해결할 수 있는 난스

새 블록을 생성하는 프로세스

  1. 새로 추가되는 트랜잭션을 담고 있는 머클 트리의 루트 얻기
  2. 새 블록 헤더의 입장에서 이전 블록 헤더를 가리키는 해시 참조 생성하기
  3. 필요 난이도 획득하기
  4. 현재 시각 읽기
  5. 1~4번에 언급된 데이터를 포함하는 예비 블록 헤더 생성하기
  6. 예비 블록 헤더에 해당하는 해시 퍼즐 해결하기
  7. 해시 퍼즐을 해결할 난스를 예비 헤더에 포함시켜 새 블록 완결하기

검증 규칙

다음의 규칙을 준수한다.

  1. 직전 블록을 가리키는 유효한 해시 참조가 있다.
  2. 트랜잭션 데이터를 갖고 있는 머클 트리의 루트를 포함한다.
  3. 정확한 난이도를 가지고 있어야 한다.
  4. 타임 스탬프는 직전 블록 헤더의 타임 스탬프 이후여야 한다. (블록과 트랙잭션 데이터들이 추가 된 순서에 따라 정렬되어 있음을 보장)
  5. 난스를 포함해야 한다.
  6. 위 5개의 데이터 조각을 함친 해시값으로 난이도를 충족해야 한다.

검증 규칙은 해시 퍼즐을 해결하고, 필요한 계산 비용을 지불한 블록만 추가되도록 보장한다. 이처럼 해시 퍼즐을 해결하여 블록체인-데이터-구조에 새 블록을 추가하는 행위를 마이닝 혹은 블록 마이닝이라 부른다.

작동하는 이유

해시 참조가 가리키는 데이터에 변경이 있으면 그 즉시 해시 참조 자체를 손상시키는 성질에서 비롯되어, 데이터 조작이 있을 경우 영향받은 모든 블록을 다시 작성하게 한다. 결국 블록체인-데이터-구조의 불변성은 해시 퍼즐의 난이도에 달려 있다. 난이도가 너무 낮으면 계산비용도 낮아져 조작의 가능성이 높아지고, 난이도가 너무 높으면 새 데이터를 추가하는 계산 비용도 높아져 새 데이터 추가에 대한 의욕이 상실될 수 있다. 따라서 블록체인 디자인 시 해시 퍼즐의 적절한 난이도를 결정하는 것이 매우 중요하다. 실제로는 새 블록이 추가되는 속도에 기초한 동적 난이도를 활용하는데, 새 블록의 추가 속도가 빨라지면 난이도를 높이고, 그 반대면 난이도를 낮추는 방식으로 적정 수준의 난이도를 유지한다.


17. 컴퓨터들이 정보를 배분하는 방법

목표

P2P 시스템을 구성하는 개별 컴퓨터들이 트랜잭션이 발생할 때마다 적절히 통보받아 자신의 트랜잭션 데이터 이력을 관리할 수 있도록 보장한다.

해결해야 할 과제

순수 분산 P2P 시스템에는 중앙 통제와 조정 장치가 없기 때문에 시스템의 모든 노드가 이러한 장치에 의지하지 않고 모든 트랜잭션 정보를 수신하도록 해주는 것이다.

아이디어

P2P 시스템을 구성하는 컴퓨터들이 정보를 공유하고 교환하도록 하는 것인데, 노드 하나가 피어 노드에게 정보를 전달받으면 다른 피어에게 그 정보를 전달하고, 그 피어가 또 다른 피어에게 전달하는 방식으로 시스템 내 모든 노드가 정보를 전달받도록 한다.

작동원리

분산 P2P 시스템의 컴퓨터는 디지털 네트워크를 통해 통신하기 때문에 노드들이 인터넷을 통해 서로 통신하도록 하는 방식으로 P2P 시스템을 구성할 수 있다. 단 노드가 네트워크를 통해 통신하고 언제든 단절과 재접속을 할 수 있다는 점이 메시지 전달에 영향을 미친다. 네트워크에서 이루어지는 메시지 전달에는 아래와 같은 특성이 있다.

  • 손실될 가능성이 있다.
  • 한 번 이상 도달할 수 있다.
  • 전송한 순서와 다른 순서로 도착할 수 있다.

이 특성때문에 통신에 장애가 발생할 수 있지만 아래의 방법으로 해결할 수 있다.

  • 새로운 정보를 받은 모든 노드는 자신이 통신하는 피어에게 전달하기 때문에 몇몇 메시지가 손실될 수 있어도 궁극적으로 모든 노드가 메시지를 수신하는 것을 보장한다.
  • 메시지는 디지털 지문 또는 암호화 해시값으로 식별 가능하므로 노드가 중복된 메시지를 식별해서 무시할 수 있다.
  • 트랜잭션 데이터와 블록 헤더에 타임 스탬프가 포함되어 있어서 노드들이 객관적인 시각 기준에 따라 메시지를 정렬할 수 있다.

P2P 시스템을 구성하는 노드 간 통신은 다음 세 가지 목적을 가진다.

  • 기존 연결 유지
  • 새로운 연결
  • 새로운 정보 배포

기존 연결 유지, 새로운 연결은 P2P 시스템 자체에 초점을 맞추고 있어서 피어 간 네트워크를 유지하고 약간의 디지털 관리 업무를 수행한다. 새로운 정보 배포는 블록체인-데이터-구조에 새로운 트랜잭션 데이터나 블록을 추가하는 것에 초점을 맞춘다.

  • 기존 연결 유지

네트워크의 각 컴퓨터는 통신하는 피어 리스트를 독립적으로 유지하고, 피어 리스트는 시스템을 구성하는 전체 노드의 부분집합이다. 주기적으로 각 컴퓨터는 피어가 연결상태인지 확인하기 위해 핑(ping)하고 메시지를 보내고, 퐁(pong)으로 대답하도록 요청한다. 만약 이 메시지에 반복적으로 대답하지 않는 피어가 있다면 리스트에서 제거한다.

  • 새로운 연결

P2P 시스템 내에 참여하고 싶은 컴퓨터는 시스템 내의 아무 노드에게나 시스템에 참여하고 싶다고 요청한다. 이렇게 요청을 받은 노드는 요청자인 피어의 주소를 피어 리스트에 추가하고 확인 응답을 보내고, 확인 응답을 받은 노드는 응답을 보낸 주소를 자신의 피어 리스트에 추가한다. 이렇게 되면 새로운 연결이 형성되어 시스템에 노드 하나가 더 증가한 것이다. 그리고 P2P 시스템에 참여할 때는 시스템의 일부인 다수의 노드와 연결을 형성한다. 특정 노드 하나의 연결이 끊어지더라도 시스템에 연결된 상태를 유지하기 위함이다.

  • 새로운 정보 배포

새로 추가될 트랜잭션 데이터와 새 블록 정보를 통신을 통해 전달하는데, 소유권 관련 정보를 공유하는 일은 다음 3가지 경우에 발생한다.

  • 지속적인 방식
    • 새로운 정보는 발생과 동시에 배포되고, 시스템에 연결된 모든 노드가 정보를 받는다.
  • 갱신 방식
    • 연결이 끊겼다가 다시 시스템에 연결되었을 때, 그 사이 놓친 트랜잭션 데이터와 블록에 대한 정보를 모두 받는다.
  • 온보딩 절차의 일부로 갱신
    • 시스템에 새로 참여한 노드는 트랜잭션 이력을 구축할 기회가 없었기 때문에 시스템 참여 직전까지 발생한 모든 트랜잭션 이력을 얻어와야 한다. 따라서 새로운 참여자가 참여하자 마자 모든 정보를 갖추어 노드가 될 수 있도록 보장한다.

작동하는 이유

앞서 살펴본 3가지 통신 방식이 새로운 컴퓨터가 시스템에 참여할 수 있도록 보장해주기 때문에 시스템이 확장되게 하면서 기존 연결 유지의 업무도 수행하게 한다. 가장 중요한 점은 블록체인-데이터-구조 내 새로 추가되는 트랜잭션 데이터와 블록에 대한 모든 정보를 P2P 시스템의 전체 구성원이 궁극적으로 받아볼 수 있도록 보장한다는 것이다.


18. 블록체인 속 무한경쟁 사회

목표

시스템의 무결성을 유지하면서 누구든지 트랜잭션 데이터 이력에 새로운 트랜잭션을 추가할 수 있도록 허용한다.

해결해야 할 과제

완전히 개방된 시스템에서 누구든 시스템에 참여하여 데이터를 전송할 수 있는데, 이 데이터들이 정확하다는 보장은 없다. 따라서 시스템을 모두에게 개방하면서도 오직 유효한 트랜잭션만 추가될 수 있도록 보장하는 것이 필요하다.

아이디어

시스템의 모든 노드가 다른 피어 노드를 감시하도록 하면서 동시에 유효하고 승인된 트랜잭션을 추가하거나 타인의 작업에 오류를 발생했을 때 적절한 보상을 해주어 동기를 부여한다.

작동원리: 구성요소들

블록체인-알고리즘은 노드가 새 트랜잭션 데이터와 블록을 어떻게 처리할지 통제하는 일련의 명령어이다. 개별적 규칙과 절차는 다음과 같다.

  1. 검증 규칙
  2. 보상
  3. 처벌
  4. 경쟁
  5. 피어 통제

1. 검증 규칙

블록체인-데이터-구조가 유효한 트랜잭션 데이터와 블록 헤더만으로 구성된 블록만을 가지도록 한다. 데이터 유효성의 검증에는 다음의 두 가지가 필요하다.

  • 트랜잭션 데이터 검증 규칙
  • 블록 헤더 검증 규칙
  • 트랜잭션 데이터 검증 규칙

형식의 정확성, 의미상 정확성, 승인의 3요소이다. 이 규칙은 블록체인의 응용분야(블록체인이 어떤 산업분야에서 사용되는가)에 따라 서로 다른 검증 규칙을 가지게 된다.

  • 블록 헤더 검증 규칙

형식과 의미의 정확성에 초점이 있고, 트랜잭션 데이터의 내용과 무관한 블록체인-데이터-구조에 정보가 추가되는 방식과 관련 있다. 개별적 해시 퍼즐을 해결한 헤더를 가진 블록만 그 다음 단계로 넘어가고, 그렇지 못한 블록은 즉시 폐기된다.

2. 보상

유효한 블록 생성을 위해 블록의 고유 해시 퍼즐을 풀어야만 한다. 이때 피어가 해시 퍼즐을 푸는 부담(에너지, 시간, 돈 등)을 짊어지도록 하는 방법은 비용에 상응하는 보상을 제공하는 것이다. 블록체인-알고리즘이 유효한 블록을 제출한 노드가 보상을 받는 방식을 정의한다.

3. 처벌

  • 생성 당시엔 인정되었지만 나중에 유효하지 않거나 무용한 것으로 판명된 블록에 대해 이미 지급된 보상을 회수한다.
  • 노드가 작업 증명을 했지만 블록이 중복되거나 너무 오래된 경우, 쓸모없는 경우에 보상을 하지 않는다.

4. 경쟁

시스템을 유지하는데 드는 비용을 줄이면서 높은 작업 품질을 성취하는 최선의 방법으로 서로 경쟁하도록 만든다. 블록체인-알고리즘은 아래 두 가지 기준의 조합으로 지속적인 경쟁을 유발하며 적절히 보상한다.

  • 속도 경쟁

해시 퍼즐에 기초한다. 유효한 블록 생성의 핵심은 작업 증명을 생성하는 것이므로, 작업 증명은 곧 새 블록의 고유 해시 퍼즐을 해결했다는 의미이다. 모든 노드는 해시 퍼즐을 푸는 경쟁에 참여하게 되는데, 이 경쟁은 어떤 노드가 새 블록을 제출함과 동시에 끝난다. 새 블록을 제출한 노드는 품질 경쟁의 유일한 후보자가 된다.

  • 품질 경쟁

제출된 블록의 정확성에 초점을 맞춘다. 새 블록은 수신한 모든 모드는 트랜잭션 데이터와 블록 헤더의 검증 규칙에 기초하여 새 블록을 검증한다. 피어 통제를 통해 매우 높은 정확성을 유지하도록 한다. 새 블록을 수령한 노드는 자신이 속도 경쟁에서 패했다는 사실을 깨달았으므로 꼼꼼한 심판관이 되고, 제출된 블록이 무효라면 또다시 속도 경쟁을 거쳐야 하므로 자신이 새 블록을 제출하여 승자가 될 수 있음을 알고 있다.

5. 피어 통제

시스템의 모든 노드는 다른 노드의 감시자인데, 새 블록을 생성하고 트랜잭션을 검사하면서도 다른 노드가 생성한 블록을 수신해서 검토하고 검증하는 역할을 한다. 즉 새 블록의 생성과 유효하지 않은 트랜잭션 데이터와 블록의 감지, 거절, 제거에 기여한다.

작동원리: 골격

시스템의 모든 노드는 다음 두 상태 중 하나에 속해 있다.

  1. 피어에 의해 생성되고 제출된 새 블록을 평가 중
  2. 스스로 새 블록을 만들어 다른 피어에게 평가받으려 열심히 노력 중

블록체인-알고리즘의 중요 역할은 모든 노드가 동일한 작업 상태를 가지도록 보장하는 것이고, 동일한 작업 상태는 모든 블록이 동일한 트랜잭션 데이터 이력을 유지하게 하는 핵심 개념이다. 상태를 결정하는 것은 개별 노드에게 도착하는 메시지인데, 각 노드가 새 블록을 담고 있는 메시지를 받자마자 평가 상태로 스위치되고, 평가 상태가 완료되자마자 새 트랜잭션을 검증하고 자체적으로 새 블록을 생성하기 위한 상태로 스위치된다.

부정직한 노드는 어떻게 처리할까?

P2P 시스템에서 가장 두드러진 부정직한 행위는 다음과 같다.

  • 다른 사람으로 위장해 트랜잭션 제출
  • 유효하지 않은 트랜잭션 데이터나 블록을 인정
  • 노드를 다운시키기 위해 수많은 트랜잭션 데이터를 전송
  • 특정 트랜잭션 데이터 처리를 거부
  • 정보 전달을 거부

이러한 부정직한 경우는 아래와 같이 처라힌다.

  • 해당 개인 키 소유자에게만 계정의 접근을 허용하는 트랜잭션의 보안 개념(비대칭 암호화 기법, 디지털 서명을 통한 식별, 인증, 승인)
  • 모든 노드가 결국 모든 정보를 수신하는 것을 보장.
  • 특정 노드가 잘못되거나 데이터 처리를 멈추어도 전체 시스템이 계속 작동하도록 보장하는 시스템 아키텍처
  • 블록체인-알고리즘

블록체인이 부정직한 노드를 처리하는 가장 큰 무기는 정직한 다수의 힘과 보상 및 처벌의 효과이다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

12. 암호화 기법을 소개합니다

블록체인에서 비대칭 암호화 기법은 사용자를 식별하고 자산을 보호하는 근간을 이룬다.

목표

고유한 소유자와 자산을 식별하고, 법적으로 허가된 사람만 그 자산에 접근할 수 있도록 보장한다.

해결해야 할 과제

블록체인은 완전 개방된 P2P 시스템이고, 누구나 접속해서 계산 자원에 기여하고 새로운 트랜잭션을 시스템에 제출할 수 있다. 단, 소유권을 다른 계정으로 이전할 권리는 오직 그 계정의 소유자에게 국한된다. 분산 시스템의 개방형 아키텍처를 해치지 않으면서 계정에 할당된 자산을 보호하는 것이 중요한 과제이다.

아이디어

누구나 자산을 보낼 수 있지만 모인 자산에는 계정의 소유자만 접근할 수 있다. 이를 공개-개인-키 암호화라고 하는데 소유권을 이전할 수 있는 계정을 식별할 때는 누구나 공개 키를 사용하고, 접근은 해당 개인 키를 가진 사람에게만 허용된다. 어디에나 있고 개방되어 있지만 열쇠가 있는 사람만이 열 수 있는 우편함의 성질과 같다.

암호화 기법의 주요 아이디어

허가받지 않은 사용자의 접근으로부터 데이터를 보호하고, 이를 위해 사용하는 것을 키(key)라 한다.

  • 암호화: 문을 잠그는 것의 디지털 구현
  • 복호화: 문을 여는 것의 디지털 구현
  • 데이터 보호화 데이터 보호 해제: 암호화와 복호화
  • 암호문: 암호화된 데이터
  • 과정: 암호화 키를 사용해 암호문 생성 -> 암호문의 보관 또는 전송 -> 암호화 키를 사용해 복호화 -> 원시 데이터 복원

대칭 암호화 기법

데이터를 암호화하고 복호화할 때 동일한 키를 사용.

동일한 키를 사용했기 때문에 암호화한 키가 있으면 동시에 복호화할 수 있는 키도 가지는 셈이다. 따라서 바람직하지 못하고 그래서 비대칭 암호화 기법이 발명되었다.

비대칭 암호화 기법

두 개의 상호보완적 키를 사용.

  • 두 키중 하나를 사용해 생성된 암호문은 오직 다른 하나의 키로만 복호화되고, 그 반대도 마찬가지이다.
  • 암호문을 생성할 때 사용한 키로는 절대 암호를 복호화할 수 없다.
  • 암호화 복호화를 할 때 항상 두 개의 키를 모두 가지고 있어야 한다. 둘 중 하나만 있으면 하는 일이 제한적이다.
  • 암호문의 생성은 하나의 키만 있어도 언제든 가능하지만 상호보완적 쌍을 이루는 다른 키가 없다면 생성된 암호문을 복호화할 방법은 없다.
  • 두 개의 키가 암호문을 생성할 수 있는 사람, 복호화할 수 있는 사람의 두 그룹으로 분리할 수 있게 해준다.
  • 암호화 복호화의 역할은 서로 바뀔 수 있지만 개인 키와 공개 키의 역할은 절대 바뀔 수 없다. 공개 키는 비가역적 함수를 통해 개인 키로부터 생성된 것이다.

실생활에서 비대칭 암호화 기법을 사용하는 방법

1. 키의 생성과 배분

보통 두 개의 키 각각의 역할을 나타내는 특별한 이름을 붙인다. 그래서 비대칭 암호화 기법을 공개-개인-키(공개-비밀-키)라고 부른다. 공개 키는 신뢰 여부와 상관없이 누구에게나 주어지기 때문에 누구나 공개 키의 복사본을 가질 수 있고, 개인 키는 안전하게 사적으로 보관해야 한다. 아래는 비대칭 암호화 기법 응용프로그램의 수행 단계이다.

  • 암호화 소프트웨어를 사용해서 개인 키를 생성한다.
  • 상호보완적인 공개 키를 생성한다.
  • 개인 키는 간직한다.
  • 공개 키는 모두에게 배부한다.

2. 키의 사용

1. 공개 -> 개인
  • 공개 키: 정보를 암호화
  • 개인 키: 정보를 복호화

누구나 암호문을 생성할 수 있지만 개인 키를 가진 소유자만이 암호문을 복호화하고 메시지를 읽을 수 있다.

2. 개인 -> 공개
  • 공개 키: 정보를 복호화
  • 개인 키: 정보를 암호화

공개 키의 복사본을 가진 사람은 누구나 메시지를 읽을 수 있지만 모두에게 공개할 메시지는 오직 개인 키를 가진 소유자만이 생성가능하다. 저작권 증명에 주로 사용하면 좋다.

블록체인에서 비대칭 암호화 기법은 언제 쓸까?

  1. 계정 식별
  2. 트랜잭션 승인
1. 계정 식별
  • 소유자와 자산의 매핑 유지를 위해 사용자와 사용자 계정을 모두 식별해야 한다.
  • 공개 -> 개인 접근 방식을 사용해서 사용자 계정을 식별하고, 사용자가 소유권을 이전한다.
  • 블록체인의 계정 번호는 공개 암호 키라서 공개된 주소를 통해 누구나 메시지를 전송할 수 있다.
2. 트랜잭션 승인
  • 소유권 양도에 관한 내용에 그 소유권자가 동의했는지를 증명하는 데이터를 항상 포함한다.
  • 개인 -> 공개 접근 방식과 유사하여, 소유자는 개인 키를 사용해 암호문을 생성하고, 다른 모든 사람은 공개 암호 키를 사용해 거래를 증명할 수 있다.
  • 공개 암호 키는 소유권을 이전하려는 계정 번호와 같다.

13. 노드 여러분, 트랜잭션을 승인합니까?

디지털 서명 만들어 검증에 사용하기. 자필 서명과 유사하게 전자 원장에 트랜잭션에 동의한다고 표시한다. 개별 트랜잭션의 보안을 위해 절대적으로 중요하다.

목표

계정의 소유자 또는 합법적 소유자만 계정 내 자산을 다른 계정으로 이전할 수 있도록 보장한다.

해결해야 할 과제

합법적 소유자로만 이전 권한 제한 + 개방성 유지

P2P 시스템은 누구에게나 개방되어 있어 모든 사람이 트랜잭션을 생성하고 시스템에 제출할 수 있지만 이전은 계정의 합법적 소유자만이 가능하다.

아이디어

자필 서명의 기능을 디지털 버전으로 만들어 계정을 식별, 인증, 허가를 가능하게 한다.

디지털 서명의 주요 기능 알아보기

개인 -> 공개 방식의 정보 흐름을 활용한다.

디지털 서명의 3대 주요 요소

  1. 서명의 생성
  • 특정 데이터에 대한 프로세스가 시작되면 입력된 데이터에 해당하는 해시값을 생성하고, 개인 키를 사용해서 암호화한다. 해시값의 암호문이 바로 디지털 서명이다.
  • 고유한 개인 키를 사용해 생성되었기 때문에 누가 생성했는지 특정할 수 있다.
  • 특정 데이터의 디지털 지문(해시값)에 의해 생성되었기 때문에 고유한 데이터이다.
  1. 서명을 이용한 데이터 검증
  • 모든 사람이 나의 공개 키를 활용하여 암호문으로부터 복호화된 값을 얻을 수 있다.
  • 메시지 수신자는 받은 데이터의 해시값을 스스로 계산해본다.
  • 수신자가 계산한 해시값과 복호화된 값이 일치하면 수신자는 아래의 두 가지 사실을 알 수 있다.
  • 나의 공개 키를 사용해 서명을 복호화할 수 있었기 때문에 내가 서명한 것이다.
  • 복호화된 암호문이 최초에 보내려고 했던 데이터의 해시값과 일치한다.
  1. 서명을 이용한 사기 판별
  • 메시지 수신자가 받은 데이터의 해시값을 스스로 생성한다.
  • 공개 키를 이용해 디지털 서명을 복호화하여 복호화된 원래 데이터 해시값을 얻는다.
  • 수신자가 계산한 해시값과 복호화하여 얻은 해시값이 다르면 이는 문제가 있다.

작동원리

  • 트랜잭션 데이터는 계정의 소유자가 소유권을 이전하는 데 동의했다는 사실을 알려준다
  • 트랜잭션의 전체 내용은 고유하므로 서명한 사람의 동의 없이 다른 트랜잭션의 서명으로 사용할 수 없다. 트랜잭션 내용이 바뀌면 소유자가 새로 서명을 생성해야 한다.
  • 소유권을 이전하려는 계정의 소유자만이 서명을 생성할 수 있다.
  • 누구나 쉽게 검증할 수 있다.

블록체인에서 디지털 서명의 용도

  • 트랜잭션 서명

    • 트랜잭션에 필요한 모든 정보를 기술
    • 트랜잭션 데이터의 암호화 해시값 생성
    • 소유권을 이전하려는 계정의 개인 키를 사용해 트랜잭션의 해시값 암호화
    • 암호문을 트랜잭션에 디지털 서명으로 첨부
  • 트랜잭션 검증

    • 서명을 제외하고 검증한 트랜잭션 데이터의 해시값 생성
    • 검증하려는 트랜잭션의 디지털 서명 복호화
    • 검증된 트랜잭션 데이터의 해시값검증하려는 트랜잭션의 디지털 서명을 복호화한 두 값을 비교한다. 값이 동일하면 소유권을 이전하려는 계정에 해당하는 개인 키 소유자가 승인한 트랜잭션이고, 다르면 그렇지 않다.

14. 블록체인-데이터-구조를 만들어봅시다

전체 트랜잭션 데이터 구축 및 유지하기

목표

전체 트랜잭션 이력을 정렬 상태로 유지한다.

해결해야 할 과제

모든 트랜잭션 데이터를 발생 순서를 유지하면서 어떠한 변경이 있을 경우 재빨리 감지할 수 있도록 저장한다.

아이디어

  • 트랜잭션이 추가된 순서대로 정렬괸다.
  • 데이터는 해시 참조를 사용해 변경-감지 방식으로 저장된다.

책을 블록체인-데이터-구조로 변환하기

  1. 페이지 의존성을 분명히 드러내기
    책에서 페이지는 번호가 연속되는지 검증하여 어떤 페이지가 없는지 바로 알 수 있다. 특히 각 페이지에 현재 번호와 직적 페이지 번호를 같이 표시하게 되면 현재 페이지와 직전 페이지 사이의 종속관계를 형성하여 어떤 방식으로 페이지 번호를 매기던 사라지 페이지를 탐색할 수 있다.
  2. 내용과 페이지 번호 분리하기
    책의 한 페이지는 내용과 페이지의 번호로 구성된다. 여기서 페이지에 있는 내용을 덜어낸 후 그 내용이 어디있는지에 대한 참조값만을 남긴다.
  3. 페이지 번호를 참조값으로 대체하기
    내용 뿐 아니라 페이지 번호도 참조값으로 대체한다.
  4. 참조값 생성하기
    암호화 해시값을 사용해서 고유한 참조값을 생성한다. 책의 페이지 참조값은 페이지의 내용(내용 참조값과 앞 페이지 참조값)에 기반해 계산한다.
  5. 책등 없애기
    책등을 없애서 페이지 정렬이 엉망이 되었다고 해도 각 페이지가 앞 페이지의 참조값을 가지고 있기 때문에 이를 이용해서 페이지에서 페이지로 역으로 추적해 순서를 알아낼 수 있다.

목표 달성: 결과 평가

  • 정보 저장으로부터 순서를 분리하고, 페이지의 물리적 위치로부터 논리적 위치(순서)를 분리해 냈다.
  • 참조값으로 해시값을 사용했기 때문에 누구나 간단한 계산을 통해 정확성을 검증할 수 있다.
  • 페이지들은 더 이상 책등에 붙어있지 않으므로 오직 직전 페이지 참조값을 이용해 페이지에서 페이지로 거꾸로 탐색할 수 밖에 없다.

변환된 책으로 블록체인-데이터-구조 이해하기

변환된 책 블록체인-데이터-구조
순서일람표의 페이지 블록 헤더
전체 순서일람표 블록 헤더의 체인
순서일람표의 페이지 참조값 블록 헤더의 암호화 해시값
내용 트랜잭션 데이터
내용 페이지 트랜잭션 데이터를 가진 머클 트리
내용 페이지를 가리키는 참조값 트랜잭션 데이터를 가진 머클 트리의 루트
순서일람표 페이지와 해당 내용 페이지로 구성된 가상의 단위 블록체인-데이터-구조의 한 블록
전체 순서일람표와 전체 내용 페이지 블록체인-데이터-구조
  1. 순서일람표 페이지와 해당 내용 페이지로 구성된 가상 단위
    전자는 해시 참조를 통해 후자를 참조함으로써 가상의 한 단위를 형성한다. 이것을 블록이라 하고, 블록들이 모두 모여 블록체인-데이터-구조를 형성한다.
  2. 순서일람표라 불리는 페이지 더미
    블록체인-데이터-구조 내 단일 블록 헤더와 동일하다. 참조를 통해 선형으로 연결되어 블록 헤더의 체인을 형성한다. 트랜잭션 데이터를 직접 저장하지 않고 해당 트랜잭션 데이터에 대한 해시 참조만 저장한다.
  3. 내용을 담고 있는 페이지 더미
    변환된 책의 내용은 블록체인에 의해 유지되는 트랜잭션 데이터와 같다. 다만 이는 이해를 돕기 위한 개념이며, 실제 블록체인 응용은 내용 데이터(트랜잭션 데이터)를 머클 트리라 불리는 데이터베이스에 직접 저장하고, 머클 트리의 루트는 블록 헤더에 저장된다.
  4. 순서일람표 페이지를 식별하고 연결할 페이지 참조값
    페이지 참조값은 블록체인-데이터-구조 내 개별 블록 헤더의 암호화 해시값과 같다. 블록 해시 또는 이전 블록의 해시라 불리고, 각 블록 헤더를 고유하게 식별하고 이전 블록 헤더를 참조하기 위해 사용된다.
  5. 내용 페이지를 식별하고 연결할 내용 참조값
    연계된 트랜잭션 데이터를 가리키는 블록 헤더의 해시 참조와 같다. 블록 헤더에 저장된 내용 참조값은 데이터베이스에 저장된 트랜잭션 데이터의 머클 트리 루트이다.

블록체인-데이터-구조에서 트랜잭션 저장하는 방법

각 블록은 블록 헤러를 가지고 있고, 이전 블록이 있으면 이전 블록 헤더를 가리키는 해시 참조를 가지고 있다. 다시 말하면 각기 다른 두 개의 머클 트리를 가리키는 해시 참조를 가지고 있다. 그리고 머클 트리 루트의 이름을 보며 이들이 가진 트랜잭션 데이터가 무엇인지 짐작할 수 있다. P2P 시스템을 생각해보면, 이 시스템에 참여했을 때 전체 트랜잭션 데이터, 모든 해시 참조값, 모든 블록헤더를 수신하게 된다. 즉 로컬 컴퓨터는 데이터를 모두 저장하고 저장된 데이터들을 가리키는 해시 참조를 가진 블록체인-데이터-구조를 생성한다. 여기서 주의할 점은 블록체인-데이터-구조에 각자의 헤더를 가진 수많은 블록이 존재하지만 전체 블록체인-데이터-구조는 오직 하나의 헤드만 가진다는 점이다.


15. 잘 뜨개질된 블록체인, 어떻게 변경할까?

새로운 트랜잭션 추가하기

최초의 블록체인-데이터-구조는 단 두 개의 트랜잭션만 가지고 있는데, 새 트랜잭션 데이터를 추가하려면 다음의 세 단계를 수행한다.

  1. 새로 추가하려는 모든 트랜잭션 데이터를 담고 있는 새 머클 트리 생성
  2. 이전 블록 헤더를 가리키는 해시 참조와 새로운 트랜잭션 데이터를 담고 있는 머클 트리의 루트를 포함하는 새 블록 헤더 생성
  3. 새 블록을 카리키는 해시 참조를 만들고 블록체인-데이터-구조의 헤드 갱신

변경 감지하기

블록체인-데이터-구조 내 변경이 발생하면 연쇄 반응을 일으켜 즉시 감지 가능하고, 변경 발생 유형은 아래와 같다.

  1. 트랜잭션 데이터의 세부 사항 변경
  2. 머클 트리의 트랜잭션을 변경하고, 해시 참조를 변경
  3. 머클 트리의 트랜잭션과 그 해시 참조를 통째로 대체
  4. 머클 트리 루트를 변경
  5. 블록 헤더 참조를 변경

위의 유형들처럼 트랜잭션이 하나 변경되면 부모 노드를 따라 연쇄반응을 일으켜 블록 헤더값이 변경된다. 이는 자신을 참조하는 블록의 헤더값에 영향을 끼치므로 모든 값에 영향을 미친다. 따라서 제대로 된 방법으로 데이터를 변경하려면 전체 해시 참조 연결고리를 모두 갱신해야하는데, 이는 변경된 데이터를 직접 가리키는 해시 참조로부터 시작하여 가장 최근 블록 헤더를 가리키는 해시 참조까지 모든 해시 참조를 갱신하여 변경 내용을 반영한다는 것이다.

의도한 변경 vs 의도하지 않은 변경

해시 참조의 성질에 의해 블록체인-데이터-구조의 해시 참조는 의도된 변경, 의도하지 않은 변경을 구분하지 않고 동일하게 인식한다. 그리고 의도와 상관없이 어느 해시 참조 중 하나라도 유효하지 않으면 전체 데이터 구조가 무효화된다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

3. 블록체인은 어떻게 작동하는가?

8. 블록체인의 청사진 그리기

목표: 소유권을 관리하는 블록체인의 개발. 완전히 개방된 환경에서 작동하는 원장의 순수 분산 P2P 시스템에서 소유권을 관리하는 소프트웨어 디자인.

출발점: 개발 환경 공유하기

  • 시스템은 순수 분산 P2P 시스템이고, 각 사용자가 기여한 계산 자원으로 이루어진다.
  • 인터넷을 사용해 개별 노드의 네트워크를 연결한다.
  • 노드의 개수는 물론 노드의 안정성과 신뢰성도 전혀 알 수 없다.
  • 목표는 디지털 재화의 소유권을 관리하는 것.

진행경로: 7가지 과제 해결하기

  1. 소유권 기술
    1. 블록체인으로 무엇을 하고 싶은지, 소유권을 어떻게 기술할 것인지를 결정한다.
    2. 트랜잭션은 소유권의 이전을 설명하는 좋은 방법이며, 트랜잭션 전체 이력은 현 소유자를 확인할 수 있는 핵심이 된다.
  2. 소유권 보호
    1. 타인이 다른 사람의 자산에 함부로 접근하지 못하도록 보호해줄 장치
    2. 암호화 기술을 통해 개별적 트랜잭션을 보호한다.
    3. 소유권을 보호하는 과정: 소유주 식별 -> 소유주 인증 -> 소유주 본인에게만 자산 접근 허가
  3. 트랜잭션 데이터 저장
    1. 트랜잭션 데이터는 소유권을 명확히 하는 핵심 요소이므로 안전한 방법으로 저장
  4. 신뢰할 수 없는 환경에 배분할 원장 준비
    1. 신뢰할 수 없는 환경에서 작동하는 원장의 분산 P2P 시스템을 디자인한다.
    2. 이 말은 신뢰를 알 수 없는 환경에 있는 각 노드가 신뢰할 수 있는 행동을 할 때 인센티브가 더 돌아가도록 설계해서 전체 환경을 확률적으로 신뢰할 수 있도록 만든다는 것.
    3. 신뢰할 수 없는 환경에서 원장의 복사본들이 신뢰할 수 없는 노드들 사이에 흩어져 있고, 원장을 통제하거나 조정하는 중앙 통제 노드가 존재하지 않아서 각 노드에게 원장의 제어를 맡긴다.
    4. 이 상황에서 트랜잭션을 바꾸지 못하도록 막아두는 것으로 원장의 위조나 조작을 막을 수 있다.
    5. 원장과 트랜잭션 이력은 한번 기록되면 바꿀 수 없게 하는 것이다.
    6. 단 블록체인-데이터-구조에서 변경을 못하게 하면서 새로운 트랜잭션을 추가할 수 없으니 추가 전용 블록체인-데이터-구조로 만들어서 새 트랜잭션을 추가할 수 있도록 승인해야 한다.
  5. 원장 배분
    1. 원장을 추가 전용으로 만들고 요청하는 누구에게나 복사본을 만들어주면 원장의 분산 P2P 시스템을 구성할 수 있다.
  6. 원장에 새 트랜잭션 추가
    1. 분산 P2P 시스템은 개별적으로 추가 전용 블록체인-데이터-구조를 유지하는 구성원들로 구성된다.
    2. 오직 유효하고 승인된 트랜잭션만 추가될 수 있도록 보장해야 한다. 이를 위해 P2P 시스템의 구성원 모두에게 새로운 데이터를 추가할 수 있도록 허용하고, 각 구성원이 P2P 시스템의 각 피어를 감독하게 한다.
  7. 어느 원장이 진실인지 판단
    1. P2P 시스템에서 개별 원장에 새로운 트랜잭션을 추가할 수 있도록 허용하면 모든 분산 P2P 시스템에서 발생하는 각 피어들이 서로 다른 트랜잭션 데이터를 받을 가능성이 생긴다.
    2. 트랜잭션 이력은 합법적 소유자를 찾기 위한 기초가 되므로 시스템 내 서로 다른 이력이 충돌해서는 안되고, 따라서 진실을 담고 있는 트랜잭션을 가려낼 기준이 필요하다.
    3. 순수 분산 P2P 시스템에는 어떤 트랜잭션 이력을 선택해야 할지 결정해 줄 중앙 통제 장치가 없기 때문에 모든 노드가 독립적인 다수 의견을 따라 진실된 트랜잭션 이력을 개별적으로 결정해서 문제를 해결하도록 한다.
    4. 블록체인이 새 트랜잭션을 추가 전용 블록체인-데이터-구조에 추가하도록 허용하여 문제를 해결할 수 있다.

9. 소유권 기록에서 모든 것이 시작된다

목표: 누가 보더라도 명확히 알 수 있게 소유권 기록하기

  • 해결해야 할 과제: 소유권에 대한 주장을 기록하고, 소유권에 대한 증거까지 제공하여 소유권 자체를 증명하는 기록 방법을 찾아낸다.
  • 아이디어: 소유권의 현 상태를 재산 목록 데이터 형태로 단순 기술하지 않고, 소유권이 이전된 이력까지 원장에 빠짐없이 기록 보관한다.

목록과 트랜잭션 데이터는 어떻게 다른가?

소유권을 기술한다는 점에서는 같고, 아래와 같은 차이점이 있다.

소유권 목록 트랜잭션 데이터
소유권 목록을 작성 트랜잭션 데이터를 작성
소유권의 현 상태를 기술 소유권의 이전 이력을 기술
e.g. 잔고증명서(은행 계좌의 현 잔고 표시) e.g. 거래내역조회서(출금, 입금 등의 내역을 모두 표시하여 보여줌)
단순하게 소유권을 주장 소유권을 설명하고 증명

작동 원리

블록체인 소유권 기록에 필요한 두 가지

  1. 소유권 이전을 기술
  2. 트랜잭션 이력을 유지

1. 소유권 이전을 기술

현 소유자의 소유권을 다른 사람에게 이전하는 행위를 트랜잭션이라 한다. 이때 데이터에 의도한 이전을 데이터에 기록하고, 이 데이터에는 소유권 이전에 필요한 모든 정보가 담긴다. 은행의 계좌이체 시 계좌이체 서식과 비슷하다. 단, 중앙 통제된 은행에서 동일한 수수료 체계를 사용하는 것과 달리 블록체인은 중앙 통제가 없는 분산 시스템이므로 통일된 수수료 체계가 없다. 블록체인은 트랜잭션을 기술하기 위해 다음의 정보를 이용한다.

  1. 다른 계정으로 소유권을 이전하려는 계정의 식별자
  2. 소유권을 이전받으려는 계정의 식별자
  3. 이전하려는 재화의 총액
  4. 이전이 완료되는 시각
  5. 이전하기 위해 시스템에 지불해야 하는 수수료
  6. 이전에 동의한다는 원소유자의 증명

2. 트랜잭션 이력의 유지

트랜잭션의 수행은 트랜잭션 데이터에 기록된 대로 소유권의 이전이 일어난다는 것이며 원장에 트랜잭션 데이터를 추가한다는 의미이다. 블록체인은 모든 트랜잭션을 일어난 순서대로(기록된 순서대로) 블록체인-데이터-구조에 저장하여 전체 이력을 유지한다.

  • 작동하는 이유: 트랜잭션 데이터는 소유권을 이전하려는 계정에 대한 모든 정보를 가지고 있어서 트랜잭션 전체 이력만 존재하면 소유권 관련 정보를 재구성할 수 있다.
  • 트랜잭션 순서가 중요하다: 트랜잭션 데이터를 합치는 이유는 소유권 이전 내역을 재구성해 현 소유권자를 찾아내고 소유권을 명확화하려는 목적에서다. 따라서 데이터를 합칠 때마다 트랜잭션이 발생했던 순서 그대로 동일하게 재현되는 것이 매우 중요하다. 트랜잭션 순서가 달라지면 데이터가 합쳐진 결과도 달라지기 때문이다.
  • 트랜잭션 이력의 무결성을 유지하기 위해 필요한 3요소
    • 트랜잭션 데이터의 이력이야말로 소유권을 관리하는 블록체인의 심장이다. 전체 시스템의 무결성을 지키려면 이력 데이터를 안전하고 완전하며 정확하고 일관되게 유지해야 한다. 따라서 블록체인은 오직 유효한 트랜잭션 데이터만 블록체인-데이터-구조에 추가되도록 보장하는 보안 수단을 제공해야 한다.
    • 트랜잭션 데이터 유효성 검사를 위한 3가지 요소
      • 형식적 정확성: 트랜잭션 데이터가 필요한 모든 정보를 정확한 형식으로 기술한다.
      • 의미상 정확성: 트랜잭션 데이터의 의미와 의도한 결과, 즉 의미상 정확성을 검증하기 위해 해당 사업 영역에 대한 지식이 필요하다. 트랜잭션 데이터의 의미상 정확성을 조사하기 위해서는 대개 업계 관행에 따른다.
      • 승인: 소유권을 이전하는 소유권자 본인만 자신을 대신해 트랜잭션을 실행해달라고 블록체인에 요청할 수 있어야 한다. 스마트 컨트랙션으로 대변되는 블록체인 트랜잭션의 특징은 단순 기록이 아닌 계약의 실행에 있고, 계좌 소유자가 정말로 소유권 이전에 동의하는지 증명할 수 있는 정보를 반드시 가지고 있어야 한다.

10. 데이터 해싱하기

해시값: 블록체인의 가장 중요한 기반 기술, 지문의 디지털 버전, 암호화 해시값, 블록체인에서 광범위하게 사용하기 때문에 암호화 해싱에 대해 알아여 블록체인에 대해 이해할 수 있다.

목표: 암호화 해싱으로 데이터 식별하기

분산 P2P 시스템에서는 엄청난 양의 트랜잭션 데이터를 다루기 때문에 고유한 트랜잭션을 식별해서 최대한 빨리 비교해야 한다. 목표는 디지털 지문(암호화 해시값)으로 트랜잭션을 식별하고, 모든 종류의 데이터를 식별할 수 있도록 하는 것이다.

작동원리

  • 해시함수: 어떤 형태의 데이터든 입력 데이터의 길이와 상관없이 고정된 길이의 숫자로 변환하는 함수
    • 한 번에 하나의 데이터만 입력받아 그 데이터를 구성하는 비트와 바이트를 이용해서 해시값 생성
    • 함수의 종류에 따라 서로 다른 길이로 생성되고, 길이를 채우기 위해 앞자리 수를 0으로 채울 때도 있다.
  • 암호화 해시 함수: 중요한 해시 함수 그룹으로 어떤 데이터라도 고유의 디지털 지문을 생성해주는 해시 함수

암호화 해시 함수의 특징

  1. 어떤 종류의 데이터든 즉시 해시값 제공
  2. 확정적
  3. 의사 난수
  4. 일방 함수
  5. 충돌 회피

1. 어떤 종류의 데이터든 즉시 해시값 제공

  1. 어떤 종류의 데이터든 해시값을 생성할 수 있다.
  2. 해시값 계산을 빠르게 할 수 있다.

이 두가지 성질을 조합하여 해시 함수가 오류 메시지 같은 쓸모없는 것을 생성하거나 결과를 반환하기 위해 긴 시간을 소비하지 않도록 해준다. 매우 중요한 특징이다.

2. 확정적

동일하게 입력했을 때 동일하게 출력해야 한다. 만약 해시값이 다르다면 입력 값이 다른 것이지 해시 함수 내부 작동에 의한 것이 아니다.

3. 의사 난수

입력 데이터가 변하면 해시값이 예측 불가하게 변해야 한다. 적은 비트만 바꿔서 입력해도 해시값이 크게 변하여 입력 데이터를 보고 해시값을 예측할 수 없어야 한다.

4. 일방 함수(비가역 함수)

출력으로 입력을 알 수 있는 방법이 존재하지 않는 함수. 해시값을 이용해 원래 입력 값을 복원하는 것이 불가능하다.

5. 충돌 회피

둘 이상의 데이터가 동일한 해시값을 생성하는 것이 지극히 어려울 때 그 해시 함수를 충돌 회피라 한다. 즉 서로 다른 데이터가 동일한 해시값을 가질 확율이 매우 낮으면 그 해시 함수는 충돌 회피이고, 이런 해시 함수로 생성된 해시값은 고유성을 지녀 데이터를 식별하는 데 사용할 수 있다. 이 말은 반대로 서로 다른 데이터가 동일한 해시값을 얻으면 충돌에 직면한다는 것이다. 이는 일어나서는 안될 일이며, 이를 회피하기 위해서 엄청난 노력이 쏟아부어진다.

해시 함수 직접 적용해 보기

해시값은 해시 수로도 불리는 데 16진수로 나타내고, 아래 예시처럼 Input값이 동일하더라도 해시 함수의 종류에 따라 출력된 해시값이 달라진다.

1
2
3
4
5
6
7
8
9
//INPUT
Hello World!

//OUTPUT
MD5: ED076287532E86365E841E92BFC50D8C
SHA1: 2EF7BDE608CE5404E97D5F042F95F89F1C232871
SHA256: 7F83B1657FF1FC53B92DC18148A1D65DFC2D4B1FA3D677284ADDD200126D9069
SHA512: 861844D6704E8573FEC34D967E20BCFEF3D424CF48BE04E6DC08F2BD58C729
743371015EAD891CC3CF1C9D34B49264B510751B1FF9E537937BC46B5D6FF4ECC8

해싱된 데이터의 5가지 패턴

하나의 데이터가 해시 함수에 입력되면 그 데이터에 해당하는 해시값이 생성되는데, 이는 모든 독립된 데이터는 자신만의 고유한 암호화 해시값을 갖는다는 것을 의미한다. 만약 서로 독립적인 데이터들의 묶음에 대해 하나의 해시값만 생성하고 싶다면 아래의 5가지 패턴을 사용해서 해시 함수를 적용한다.

  1. 독립 해싱
    1. 각 데이터에 대해 독립적으로 해시 함수를 적용한다.
  2. 반복 해싱
    1. 해시 출력값에 해시 함수를 한번 더 적용한다.
    2. 해시값은 그 자제로 하나의 데이터로 간주할 수 있기 때문에 해시값을 다시 해시 함수에 입력해서 그에 해당하는 또 다른 해시값을 계산한다.
  3. 결합 해싱
    1. 하나 이상의 데이터에 해싱을 한 번만 적용하여 단일 해시값을 얻는다.
    2. 독립된 데이터들을 합쳐 하나의 데이터로 만들고 합쳐진 데이터의 해시값을 계산한다. (제공된 모든 데이터를 한꺼번에 결합)
    3. 주어진 시간 안에 가용한 모든 데이터의 집합에 단일 해시값을 부여할 때 유용하다.
    4. 다만 리소스(계산 자원, 시간, 기억 공간 등)가 많이 들기 때문에 개별 데이터가 크지 않을 때만 사용한다.
    5. 결합된 데이터만 해시 함수에 전달되었기 때문에 개별 데이터의 해시값은 알 수 없다는 단점이 있다.
  4. 순차적 해싱
    1. 새로운 데이터가 도착할 때마다 기존의 해시값이 새로운 데이터와 합쳐져서 해시값을 즉시 갱신한다.
    2. 결합 해싱과 반복 해싱을 동시에 사용하는 것이다.
    3. 단일 해시값만 유지하면서 새로운 데이터가 도착하는 즉시 해시값을 갱신하고자 할 때 유용하다.
    4. 특정 시점의 해시값으로 해당 데이터가 도착한 시점을 추적할 수 있다.
    5. old + new => brand new
  5. 계층적 해싱
    1. 두 해시값에 결합 해싱을 적용한다. 그러면 최상위에 단일 해시값을 가지는 작은 계층이 형성된다.
    2. 결합 해싱과 유사하게 데이터 묶음에서 단일 해시값을 얻고자 하는 아이디어를 가지고 있다.
    3. 길이가 매번 변하는 입력이 아니라 늘 일정한 길이의 해시값을 합치기 때문에 효율적이다.
    4. 매 단계에서 오직 2개의 해시값만 결합한다.

11. 해시값은 어디서 어떻게 사용될까?

1. 데이터 비교

  • 목표: 데이터의 크기나 내용과 상관없이 오직 숫자 두 개만 대조해서 쉽게 비교한다.
  • 아이디어: 데이터 내용 전체를 일일이 비교하는 대신 암호화된 해시값을 비교한다.
  • 작동 원리: 비교하려는 모든 데이터의 암호화 해시값을 계산한 다음 서로 비교한다. 해시값이 다르면 비교 데이터도 다르고, 해시값이 동일하면 입력 데이터가 동일한 것이다.
  • 작동하는 이유: 암호화 해시값을 통해 데이터를 비교할 수 있는 암호화 해시 함수의 충돌 회피 성질 때문.

2. 데이터의 변경 감지

  • 목표: 데이터가 일정 시간이 경과되었거나 타인에게 전송한 후, 또는 데이터베이스에 저장한 후 변경되었는지 확인한다.
  • 아이디어: 과거에 기록해 둔 암호화 해시값과 검사하려는 데이터에서 새로 생성한 암호화 해시값을 서로 비교하여 변경되었는지 알아낸다. 해시값이 동일하면 데이터가 변경되지 않은 것이다.
  • 작동 원리: 변경되면 안되는 데이터의 암호화 해시값을 만들어두고, 데이터가 변경되었는지 확인이 필요해질 때 데이터의 암호화 해시값을 다시 생성한다. 그리고, 새로운 해시값과 이전의 해시값을 비교한다. 데이터 전송 전에 해시값을 생성한 후, 수신한 사람이 새로 해시값을 생성해서 그 두 값을 비교하면 전송 도중에 데이터가 변경되었는지 여부를 확인할 수 있다.
  • 작동하는 이유: 데이터의 변경 감지는 어떤 이벤트 전후의 해시값 비교를 통해 이루어지고, 암호화 해시 함수의 충돌 회피 성질 때문에 가능하다.

3. 변경-감지 방식의 데이터 참조

블록체인은 해시 참조에 크게 의존하기 때문에, 해시 참조를 꼭 이해해야 한다.

  • 목표: 어딘가에 저정된 데이터를 참조하여 데이터가 변경되지 않았다는 것을 보장한다.
  • 아이디어:
    • 데이터와 그 데이터가 저장된 장소 정보를 결합하여 해시값으로 암호화한다. 데이터가 변경되면 데이터와 저장 장소에 대한 정보가 일치하지 않아 유효하지 않은 참조가 된다.
    • 기술적인 오류나 누군가의 고의로 인해 사용자도 모르게 참조 데이터가 변경되었을 때 원본 데이터를 추출하지 못하도록 보호한다.
    • 해시 참조는 한번 생성된 이후 절대 변경되어서는 안되는 데이터에 사용한다.
  • 작동 원리:
    • 컴퓨터 프로그램은 데이터가 저장된 장소를 기억했다가 나중에 추출하기 위해 참조값을 사용하는데, 해시 참조는 참조값으로 암호화 해시값을 활용한다.
    • 해시 참조로 참조 데이터가 만들어진 뒤로 데이터가 변경되었는지 여부를 검증한다. 참조한 데이터가 변경되었다면 원래 있던 참조값으로 더이상 데이터를 추출할 수 없기 때문이다.
  • 작동하는 이유: 암호화 해시값을 활용하는 것이 핵심이다. 해시값은 고유한 지문과 같은 것이므로 서로 다른 데이터 조각들이 동일한 해시값을 가질 가능성은 극히 드물고, 참조가 깨졌다면 데이터가 변경되었다는 증거가 된다.

4. 변경-감지 방식으로 데이터 저장

  • 목표: 변경되어서는 안되는 방대한 데이터, 즉 트랜잭션 데이터를 저장하고, 어떠한 변경도 빠르고 손쉽게 발견할 수 있다.
  • 아이디어:
    • 데이터를 저장할 때 다른 데이터를 가리키는 해시 참조를 함께 저장할 수 있는데, 가리킨 데이터가 또 다른 데이터를 가리키는 해시 참조와 함께 저장하는 방식으로 연속적으로 긴 체인을 형성할 수 있다.
    • 어느 한 데이터라도 해시 참조가 변경이 되면 전체 해시 참조가 손상되고 이것이 데이터가 변경되었다는 증거가 된다.
  • 작동 원리:
      1. 체인 방식
      • 연결 리스트(linked list)라 불리고, 각 데이터가 가른 데이터의 해시 참조를 가지는 구조.
      • 데이터가 순차적으로 나타날 때 유용하다.
      • 가장 최근에 추가된 데이터를 참조하는 해시 참조를 헤드(head)라 부르고, 헤드만 있으면 체인으로 연결되어 있는 전체 데이터에 대해 역순으로 모두 접근할 수 있다.
      1. 트리 방식
      • 머클 트리(merkle tree)라 불리고, 해시 참조와 트랜잭션 데이터가 트리 형태로 연결되어 있다.
      • 같은 시각에 존재하는 모든 데이터를 한데 묶어 단일 해시 참조로 접근하고자 할 때 유용하다.
      • 최초의 트랜잭션 데이터에서 시작하여 각각에 대한 해시 참조를 생성하고, 각각의 해시참조가 둘씩 쌍을 이루게 한 후, 각 해시 참조 쌍을 가리키는 해시 참조를 또 생성하고, 이런 과정을 단일 해시 참조만 남을 때 까지 반복한다.
      • 최종적으로 남은 단일 해시 참조는 머클 트리의 루트라 부른다.
  • 작동하는 이유: 데이터와 해시 참조를 연결하여 결합하므로 변경-감지 방식으로 데이터를 저장할 수 있다. 그러므로 이 구조에서 손상된 참조가 있다면 데이터가 변경되었다는 증거가 된다.

5. 시간-소모적 계산량 유발

  • 목표: 해시값이 컴퓨터끼리 퍼즐 대결을 하는 데 사용될 수 있고, 컴퓨터의 계산 자원을 사용해야만 해결할 수 있는 퍼즐이 필요할 때가 있는데, 이 퍼즐을 푸는 유일한 방법은 오로지 엄청난 양의 컴퓨터 계산 자원에 의존하는 것뿐이어야 한다. 해시값의 이런 용도가 블록체인의 가장 중요한 개념 중 하나이다. (비트코인을 캔다는 의미의 ‘채굴’은 블록을 만드는 행위이고, 직접 새 블록을 만들려면 반드시 해시 퍼즐을 풀어야 한다.)
  • 아이디어: 비밀번호를 정확히 눌러야만 열리는 특수 자물쇠처럼 비밀번호를 알 때까지 가능한 모든 수의 조합을 반복해서 시도하는 방식이다. 해시 퍼즐은 오직 시행착오로만 해결할 수 있다.
  • 작동 원리: 해시 퍼즐의 요소.

    • 변경되면 안 되는 주어진 데이터
    • 자유롭게 변경 가능한 데이터, 난스(nonce)
    • 적용할 해시 함수
    • 결합 해싱의 해시값에 주어진 제약 조건, 난이도(difficulty level)

      데이터와 난스에 결합 해싱을 적용해 도출한 결과 해시값이 주어진 조건을 만족해야 한다.

      • 난스를 추측하고 데이터와 결합해 해시 함수를 사용해 해시값을 계산한 후 주어진 제약조건에 따라 결과 해시값을 평가한다.
      • 해시값이 제약조건을 만족하면 퍼즐이 풀린다. 이렇게 어떤 난스가 데이터와 결합해 생성한 해시값이 제약조건을 만족했을 때 그 난스를 해답이라 부른다.
      • 해시 퍼즐을 해결했다면 항상 그 특정 난스(해답)을 보여줘야 한다.
    • 해시 퍼즐의 난이도
      • 해시값이 특정 제약조건을 만족시키는 것이 해시 퍼즐의 핵심
      • 해시 퍼즐에 사용하는 조건은 임의로 만들어서 안되고 표준화되어 컴퓨터끼리 서로 시합할 수 있어야 한다.
      • 해시 퍼즐에서 제약조건을 난이도라 부른다.
      • 난이도의 숫자는 해시값의 맨 앞 자리부터 최소 하나 이상의 0으로 채워진다. (난이도 10은 해시값의 맨 앞자리부터 연속된 10자리 이상이 0이어야 한다는 뜻이다.)
  • 작동하는 이유: 해시 퍼즐은 해시 함수가 일방향 함수라는 성질에 전적으로 의존해 작동하기 때문에 제약조건을 연구하여 역함수로 해시 퍼즐을 해결하는 것은 불가하다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

2. 왜 우리에게 블록체인이 필요한가?

4. 블록체인에게 떨어진 미션

P2P 시스템의 지속가능성은 신뢰와 무결성에 달려 있다

신뢰와 무결성은 동전의 양면과 같다. 신뢰가 사전에 형성되고 지속적인 상호작용의 결과에 따라 더욱 굳어지거나 약해진다. P2P 시스템은 시스템을 믿는 사람들이 일단 합류하여 시스템에 기여한다. 이후 상호작용 결과가 만족스러우면 계속해서 시스템의 구성원으로 남는다. 사용자들이 계속해서 구성원으로 참여할 수 있도록 시스템에 대한 신뢰를 강화시키려면 시스템의 무결성이 필요하다. 이것이 부족하면 신뢰가 낮아지고 소비자를 잃게 된다.

그렇다면 순수 분산 P2P 시스템의 무결성을 확보하고 유지할 수 있는 방법은 무엇일까?

  • 전체 노드 또는 피어의 개수를 아는가?
  • 각 피어의 신뢰성에 대해 어느 정도 알고 있는가?

분산 P2P 시스템 내의 노드 개수각 노드의 신뢰성을 알고 있다면 무결성을 확보할 가능성이 더 커진다.

P2P 시스템의 무결성을 위협하는 두 가지 요소

  1. 기술적 결함

P2P 시스템은 사용자의 개별 컴퓨터로 구성되는데 기계장비라는 것은 언제든 고장이나 오류를 일으킬 수 있기 때문에 개별 컴퓨터나 네트워크 장비 등이 고장이나 오류를 일으켰을 때 대처할 수 있어야 한다.

  1. 악의적 피어

기술적인 원인이 아닌 시스템을 자신의 이릭을 위해 착취하려는 악의적 사용자들이 있고, 이런 사용자들이 P2P 시스템의 신뢰성을 무너뜨린다. 다른 피어를 믿지 못하게 된다면 시스템 사용자들은 시스템을 떠날 것이다.

블록체인이 해결해야 할 과제는 개수도 알려져 있지 않고 신뢰성과 안정성도 알 수 없는 피어들로 구성된 순수 분산 P2P 시스템의 무결성을 확보하고 유지하는 것이다. 컴퓨터 과학 분야에서는 비잔틴 장군 문제라 한다.


5. 그래서 블록체인이 뭔가요?

  1. 데이터 구조의 명칭
  2. 알고리즘의 명칭
  3. 기술묶음의 명칭
  4. 일반 응용분야를 가지는 순수 분산 P2P 시스템을 포괄하는 용어
  1. 데이터 구조의 명칭

    블록이라 불리는 단위에 모인 모든 데이터

블록체인은 책을 구성하는 페이지들과 유사하게 마치 체인처럼 서로 연결되어 있어서 블록체인이라는 이름이 붙었다. 데이터 구조에서 데이터 블록의 연결은 책의 페이지 번호 매김 방식과는 다른 특수한 번호 매김 방식을 사용한다.

  1. 알고리즘의 명칭

    순수 분산 P2P 시스템에서 여러 블록체인-데이터-구조 내의 정보 내용을 민주주의 투표 방식과 비슷한 방법을 써서 서로 협상하는 일련의 명령어

  2. 기술묶음의 명칭

    블록체인-데이터-구조, 블록체인-알고리즘, 암호화 및 보안 기술의 조합을 의미.

이들의 조합은 응용분야와 상관없이 순수 분산 P2P 시스템의 무결성을 확보하는 데 이용될 수 있다.

  1. 일반 응용분야를 가지는 순수 분산 P2P 시스템을 포괄하는 용어

    블록체인-기술묶음을 활용하는 거래장부(원장ledger)들의 순수 분산 P2P 시스템을 지칭하는 포괄적인 용어

이 맥락에서 쓰이는 블록체인은 순수 분산 시스템을 구성하는 한 부분인 소프트웨어의 단위를 의미하는 것이 아니라 순수 분산 시스템 전체를 의미한다.

이 책에서는 블록체인을 블록체인-기술묶음을 활용하는 원장의 순수 분산 P2P 시스템을 지칭하는 포괄적인 의미로 사용.

블록체인이란?

무결성을 확보하고 유지하기 위해 순서에 따라 연결된 블록들의 정보 내용을 암호화 기법과 보안기술을 이용해 협상하는 알고리즘으로 구성된 소프트웨어 요소를 활용하는 원장의 순수 분산 P2P 시스템.

보통 블록체인의 목적을 디지털 화페의 소유권을 관리하기 위한 것으로 두기 마련인데, 암호화폐의 소유권을 관리하는 것은 블록체인 응용분야의 하나일 뿐이며, 그 자체가 블록체인의 전부는 아니다.

다만 블록체인이 유독 디지털 재화의 소유권 관리 측면이 부각된 것은 1. 이해하고 설명하기 쉽고, 2. 경제에 가장 크게 영향을 미치는 실사례이기 때문이다. 소유와 소유권 강화는 인간사회의 핵심 요소이기 때문에 소유권 관리 방식을 혁신적으로 바꿀 수 있는 블록체인이 엄청난 변혁을 불러올 수 있는 기술이라 여겨진다.


6. 소유권의 본질 이해하기

소유권을 증명하기 위해 단 한 명의 목격자라도 확보하는 것은 좋은 일인데, 특히 독립적인 다수의 목격자를 확보하는 것이 중요하다. 이 아이디어가 블록체인의 핵심 개념 중 하나이다.

소유권 입증에 필요한 3요소

  • 소유자가 누구인가
  • 소유 물건은 무엇인가
  • 소유자와 물건의 매핑

소유권 사용은 오직 허가박은 사람만 그 자산을 사용하도록 인증하고 승인하면서 식별도 필요하다.

속성으로 보안의 3가지 개념 이해하기

  • 식별 : 누구라고 주장하는 것
  • 인증 : 당신과 당신이라고 주장하는 누군가가 일치하는지 증명
  • 승인 : 사전에 인증된 개체에 대해 무엇인가에 대한 접근을 허가하는 것.

  • 식별: 이름 또는 다른 식별자를 사용해 ‘누군가’라고 주장하는 것. 식별을 통해 대상이 ‘누구’라고 주장할 수 있는데 그렇게 주장한 사람과 누구로 주장된 사람이 일치하는지는 증명할 수 없다.

  • 인증: 어떤 사람이 다른 누군가를 사칭하는 것을 방지하기 위한 것. ‘누구’라고 주장한 사람과 ‘누구’로 주장된 사람이 일치하는지를 ‘무엇’을 통해 증명한다. 이때 ‘무엇’은 ‘누구’라고 주장되는 그 사람만의 고유한 무엇이어야 한다. (e.g. 지문, 얼굴 사진 등 고유성을 식별해 줄 수 있는 것.)

  • 승인: 식별된 사람의 성질과 특성에 기반해 특정 자원이나 서비스에의 접근을 허가해 주는 것. 성공적인 인증과 함께 인증된 특정인이 가진 특성과 권리에 대한 평가를 토대로 얻는 최종 결과. 항상 이미 인증된 개체의 특성이나 성질을 특정 규칙과 비교 평가한 다음 이루어진다.

원장은 소유권을 증명하기도, 이전하기도 한다

원장은 두 가지 상반된 역할을 수행한다.

  • 원장에서 읽은 과거 데이터를 이용해 소유권을 증명해 주는 수단의 역할
  • 소유권의 이전이 발생한 경우 원장에 새로운 데이터를 생성하여 이 사실을 문서화해두는 역할

원장이 누구에게나 공개되어 있다면 소유권 증명은 쉬워진다. 따라서 투명성은 소유권을 증명하는 기초가 되고, 소유권의 이전은 법적인 소유자에게만 배타적으로 허가된다.

원장의 소유권 증명 원장의 소유권 이전
소유권 증명 소유권 이전
투명성 개인정보 보호
데이터 읽기 데이터 쓰기
데이터 이력 사용 신규 데이터 생성
상태 유지 상태 변경

원장은 위와 같이 두 가지 상반된 역할들을 수행하는데 블록체인도 이와 비슷하다. 누구나 읽을 수 있게 개방된 원장과 유사한 데이터 구조를 사지는 거대한 분산 P2P 시스템이기 때문이다.

소유권 관리자로 임명된 블록체인

정부 규제 원장처럼 형식을 갖춘 목격자를 만드는 것이 자산의 소유권을 명확히 하는 핵심 요소인데, 독립적인 목겨자들이 많아서 상호 영향을 받지 않는 증언을 많이 확보하는 것이 진실을 찾기 위한 이 접근방식의 핵심이다. 이러한 방식을 원장의 소유권 명확화에 적용하면 조작될 위험이 있는 원장을 하나만 유지하는 대신 원장의 순수 분산 P2P 시스템을 활용해 다수의 노드가 동의하는 진실을 이용해 소유권을 확인하면 된다. 즉 하나의 중앙 통제된 원장은 훼손이나 위조의 위험이 있기 때문에 하나의 중앙 통제된 원장 대신 소유권을 기록하는 개별 원장들의 그룹을 형성한 뒤 대다수의 개별 원장이 동의하는 진실을 사용해 소유권을 관리한다.

  • 소유권 관리를 위해 사용된 개별 원장은 소유권 관련 데이터를 저장하기 위해 사용된 블록체인-데이터-구조 하나와 같다.
  • 개별 원장들은 P2P 시스템의 컴퓨터(노드)에 저장된다.
  • 블록체인-알고리즘은 개별 노드들이 최종 판결의 기초가 되는 하나의 일관된 소유권 상태에 집단적으로 도달하게 해준다.
  • 시스템의 무결성이란 소유권에 대한 진실을 판단할 능력을 말한다.
  • 식별, 인증, 승인, 데이터 보안을 믿을 수 있는 수단으로 만들기 위해 암호화 기법이 필요하다.

7. 이중사용, 블록체인이 해결한다

P2P 시스템의 취약성을 이용한 범죄 예방책

이중사용은 이중분양 사기와 비슷한데 두 사람이 동시에 같은 집을 소유할 수 없고, 둘 중 하나만 새로운 합법적 소유자가 되어야 하는 상황을 말한다. 이 책에서는 이중사용을 원장의 순수 분산 P2P 시스템에 발생할 수 있는 취약성을 의미하는 말로 사용하였다. 예를 들어 P2P 시스템에서 소유권이 다른 사람에게 이전될 때 모든 원장이 소유권 이전 사실이 기록된 최신 버전으로 갱신되어야 하는데, 여기에는 시간이 좀 걸리기 때문에 어떤 피어는 최신 정보를 알고 잇고, 다른 피어는 아직 그 정보를 모르는 상태가 있을 수 있다. 그렇기 때문에 예를 들어 만약 A가 B에게 소유권을 이전한 사실을 다른 피어들이 알기 전에 악의적으로 다른 대상에게 소유권 이전을 시도하면 아직 소유권 이전에 대한 사실을 모르는 피어들은 이를 승인하게 된다. 이처럼 소유권을 중복해서 판매할 수 있는 문제점이 생긴다.

대표적인 이중사용 문제 3가지 유형

1. 디지털 재화를 복사해 발생하는 이중사용 문제

컴퓨터 데이터는 별다른 제약 없이 복사할 수 있는 특성이 있어서 디지털 재화를 비롯한 소유자가 둘 이상일 수 없는 모든 데이터에 문제를 야기한다. 디지털 화폐를 복사해서 동일한 돈을 반복해서 지불에 사용하는 행위이다.

2. 원장의 분산 P2P 시스템에서 발생하는 이중사용 문제

모든 요소들이 소유권 이전과 같은 정보를 전달받기까지 시간이 걸리는 문제 때문에 일부 피어가 상이한 소유권 정보를 가지는 상황이 발생한다. 모든 피어가 동시에 최신 정보를 갖지 못하면 먼저 최신 정보를 습득한 누군가가 이를 악용해서 이중 사용할 우려가 있다.

3. 순수 분산 P2P 시스템의 무결성이 침해된 이중사용 문제

이중사용을 추상화해서 바라보면 분산 P2P 시스템 내 데이터의 일관성을 유지하는 문제이며, 이는 시스템 무결성의 한 측면이기도 하다. 따라서 이중사용은 시스템 무결성이 침해된 특정 사례라 할 수 있다.

이중사용 문제를 해결하는 방법

1. 디지털 재화를 복사해 이중으로 지불하는 문제 해결

디지털 재화를 나타내는 데이터와 소유자를 매핑하는 수단만 있으면 해결 가능.

2. 원장의 분산 P2P 시스템에서 발생한 이중사용 해결

응용분야는 물론 아키텍처까지 알고 있을 때 블록체인이란 용어 자체가 원장의 분산 P2P 시스템의 이중사용 문제를 없애주는 해결책.

3. 분산 P2P 시스템의 무결성이 침해된 이중사용 해결

시스템의 아키텍처는 특정되었지만 응용분야는 특정되지 않았을 때, 시스템의 구체적인 용도와 상관없이 분산 P2P 시스템의 무결성을 확보하고 유지하는 것에 초점이 있다. 단 분산 P2P 시스템의 용도가 무엇인지에 따라 무결성의 내용과 의미가 결정된다. 따라서 블록체인-기술묶음이 시스템의 무결성을 확보하고 유지할 수 있는 적절한 도구인가는 응용분야의 목적을 알아야만 판단할 수 있다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

블록체인의 기본 개념을 제대로 알지 못하면 블록체인의 전반적인 가치나 잠재적인 영향력을 가늠할 수 없을뿐더러 블록체인이 창출하는 부가가치를 이해하기가 어렵다. 또한 (블록체인을 포함해서) 새로운 기술에 대한 개념적 이해가 부족하면 자극적이고 과장된 광고에 휩쓸려 비현실적이고 실현 불가능한 망상에 사로잡히기 쉬워 나중에 실망할 수도 있다. 따라서 이 책은 ‘블록체인의 기본 개념’에 집중한다.
<책 서문 발췌>

1. 소프트웨어 공학에서 변하지 않는 주요 개념들

1. 시스템을 보는 눈 장착하기

시스템을 계층과 측면으로 분리해 분석하기

시스템을 개념적으로 분리하기 위해 사용하는 구분 기준은 다음의 두 가지다.

  • 응용계층 vs 구현계층
  • 기능적 측면 vs 비기능적 측면

1. 응용계층 vs 구현계층

  • 응용계층: 사용자의 요구사항
  • 구현계층: 요구사항을 실현. 구성요소는 본질적으로 기술적이다. 응용계층에서 사용자의 필요사항을 만족시키기 위한 수단.

2. 기능적 측면 vs 비기능적 측면

  • 기능적 측면: 시스템이 무엇을 하는가? 동사적 역할. e.g. 네트워크를 통한 데이터 전송, 음악 연주 등
  • 비기능적 측면: 시스템이 무엇을 어떻게 하는가? 부사적 역할. e.g. 멋진 인터페이스, 실행속도가 빠른 소프트웨어
    • 보안: 정확성을 포함하는 개념
    • 무결성(integrity): 시스템이 의도한 대로 작동하는 것. 보안과 정확성 모두를 포함하는 개념

사용자는 응용계층의 기능적 측면을 중시한다.

응용계층의 기능적 측면은 사용자의 명확한 요구사항들이므로 시스템에서 가장 중요하게 부각된다. 반면 구현계층의 비기능적 요소는 시스템의 주요 요소가 아니라 아주 당연한 것으로 여겨진다. 그런데 비기능적 요소 중 무결성 같은 것은 눈에 보이지 않지만 매우 중요하다.

무결성의 주요 요소

  • 데이터 무결성: 시스템에서 사용하고 유지 관리하는 데이터는 완전하고 정확하며 모순이 없다.
  • 작동 무결성: 시스템은 의도한 대로 작동하며 논리적 오류가 없다.
  • 보안: 시스템은 허가받은 사용자에게만 데이터 및 기능에 대한 접근 권한을 부여할 수 있다.

2. 큰 그림으로 바라보기

노드, 컴퓨터, 피어는 동일한 의미를 가질 때가 많지만 반드시 그렇지 않다. 구조에 따라 단일 컴퓨터가 복수의 노드 역할을 할 수 있고, 하나의 노드에 복수의 컴퓨터가 연결되어 있을 수도 있다.

  • 피어:
    • 전체 시스템 노드 중 나와 직접 연결된 노드. 이더리움에서 구분해서 사용한다.
    • 일대일의 의미를 강조하거나 노드를 사용해 특정 행위를 하는 사람의 관점에서 피어라 부름.
  • 노드: 보통의 책에서 전체 시스템 내의 한 요소임을 강조할 때 사용.
  • 컴퓨터: 공유하는 자원 측면에 대해 기술할 때.

소프트웨어 아키텍처란 무엇이고 블록체인과 어떤 관계인가?

소프트웨어 시스템을 구현하는 방법은 여러가지가 있는데, 시스템 아키텍처는 필수적으로 결정해야 한다. 시스템 아키텍처는 구성요소를 구조화하고, 구성요소 간 관계를 설정하는 방식을 의미한다. 주로 사용되는 아키텍처는 중앙 통제 방식,분산방식이 있다.

  • 중앙 통제 방식
    • 구성요소들이 하나의 중앙요소에 연결되며, 중앙 요소를 가운데 두고 나머지 요소들이 둘러싸고 있는 구조.
    • 모든 구성요소끼리 직접적으로 연결되지 않고 오직 중앙 요소와만 직접 연결되어 있다.
  • 분산 방식
    • 시스템을 통제하거나 조정하는 요소 없이 서로 연결된 네트워크 구조를 형성.
    • 모든 구성 요소와 직접적으로 연결된 노드는 단 하나도 없지만 모든 노드는 간접적으로 서로 완전히 연결되어 있다.

분산 시스템의 장점

  1. 계산 능력이 뛰어나다.

    1. 서로 연결된 모든 컴퓨터의 계산 능력이 합쳐져 발현되기 때문에 단일 컴퓨터보다 더 강력한 계산 능력을 가진다.
  2. 비용이 절감된다.

    1. 여러 대의 컴퓨터로 구성되므로 초기 구성 비용은 슈퍼 컴퓨터보다 많지만 유지 운영 비용은 훨씬 적다. 또 슈퍼 컴퓨터와 달리 개별 컴퓨터가 교체 될 때 전체 시스템에 별 영향을 끼치지 않는다.
  3. 더 안정적이다.

    1. 시스템을 구성하는 개별 컴퓨터가 고장나면 나머지 구성요소들이 그 일을 대신하기 때문에 전체 네트워크는 문제없이 잘 작동한다.
  4. 자연스럽게 확장된다.

    1. 앞서 계산 능력은 연결된 모든 컴퓨터의 계산 능력을 합친 것이라 했으므로, 시스템에 컴퓨터를 추가하면 손쉽게 전체 계산 능력을 높일 수 있다.

분산 시스템의 단점

  1. 조정 오버헤드가 발생한다.

    1. 중앙 통제 방식과 달리 중앙 요소가 존재하지 않기 때문에 구성요소들 스스로 조정을 해야하는데 이 조정이 쉽지 않아 많은 자원이 소모되는 오버헤드가 발생한다.
  2. 통신 오버헤드가 발생한다.

    1. 조정을 위해서 통신이 필수적이고, 이 통신을 위해 계산 능력의 일부가 통신 프로토콜 지원 및 메시지 송수신 및 처리에 소모된다.
  3. 네트워크 의존도가 높다.

    1. 컴퓨터들은 네트워크를 통해 통신하는데, 모든 네트워크는 자체적인 결함과 장애 가능성이 내재되어 있기 때문에 통신과 조정에 영향을 끼친다. 그리고 네트워크가 없으면 각 구성요소 간의 협력이 불가하기 때문에 네트워크 의존도가 높다.
  4. 프로그램이 복잡해진다.

    1. 앞서 말한 단점들을 해결하기 위해 중앙 통제 방식에서는 필요없는 조정, 통신, 네트워크 이용에 관련된 추가적인 문제 해결 등이 필요하다.
  5. 보안에 신경써야 한다.

    1. 네트워크를 통한 통신은 계산 작업 시 데이터의 전송과 공유가 꼭 필요하다. 그러나 이런 행위로 인해 악의를 가진 개체가 정보에 접근하여 악용하는 보안문제가 발생할 수 있다.

사용자가 많아질수록 더 강력해지는 분산 P2P 시스템

Peer to Peer 네트워크 또는 P2P 네트워크. 분산 네트워크의 특수한 형태

개별 컴퓨터(노드)로 구성된 시스템으로 중앙 노드의 조정 없이 네트워크의 모든 구성원이 서로에게 계산 자원을 제공한다. 네트워크의 각 노드는 시스템 내에서 동등한 권리와 역할을 가지고, 모두가 자원의 공급자인 동시에 소비자이다. 사용자들의 컴퓨터를 분산 시스템을 구성하는 노드로 만든다는 아이디어를 배경으로 다양한 분야에 응용이 가능하다.

중앙 통제와 분산 시스템의 장점만 모은 혼합 시스템

중앙 통제 시스템과 분산 시스템은 정반대의 구조를 가지고 있는데, 이 각각의 강점만 결합한 혼합 시스템이 있다.

  • 분산 시스템 내의 중앙 통제
    분산 시스템 내부에 중앙 통제 요소를 구축한 아키텍처로 겉보기에 분산 시스템을 이루는 것처럼 보이지만 모든 노드는 중앙의 큰 원에 직접 연결되어 있다.

  • 중앙 통제 시스템 내의 분산 시스템
    겉보기에 주변 노드가 모드 중앙의 큰 원에 직접 연결되어 있는 중앙 통제 시스템처럼 보이는데, 중앙의 큰 원 내부를 들여다보면 분산 시스템으로 구성되어 있다. 심지어 주변 구성요소들은 중앙 요소가 분산 시스템으로 이루어져 있는지 모를 수도 있다.

분산 시스템인지 구분하는 방법

전체 시스템을 동시에 종료할 수 있는 단일 구성요소가 있는지 찾는다. 이런 요소가 있다면 분산 시스템이 아니다.

큰 그림으로 본 블록체인의 목적

소프트웨어 시스템 디자인에서 아키텍처의 선정은 시스템 구현 관점에서 목적 달성을 위한 수단이기 때문에 분산 방식 또는 중앙 통제 방식 중 어떤 것으로도 구현할 수 있다. 다만 어떤 아키텍처를 선택하느냐에 따라 시스템이 기능적, 비기능적 측면을 달성하는 방법에 영향을 미친다. 특히 무결성에 대해서 두 가지 아키텍처가 매우 다른 방식으로 접근하기 때문에 이런 점에서 블록 체인이 중요하다. 블록체인은 분산 시스템이 무결성을 확보하게 해주는 도구이며, 분산 시스템의 무결성을 구현하고 유지하는 것이 바로 블록체인의 목적이다.즉 구현계층의 비기능적 측면을 성취하게 해주는 도구이다.


3. P2P 시스템의 엄청난 잠재력

P2P 시스템이 세상을 어떻게 바꿀 것인가?

P2P 시스템이 음악산업을 어떻게 변화시켰는지를 보면서 블록체인과 어떤 관계가 있는지 살펴본다. 전통적인 음악산업은 음악가와 스튜디오가 계약을 맺고, 스튜디오가 음악가의 노래를 녹음해서 다양한 유통채널을 통해 판매하는 구조로 운영되어 왔다. 스튜디오가 사실상 음악가와 음악을 청취하고자 하는 사람들 사이의 중개자역할을 한 것이다. 중개자로서 스튜디오는 음반제작 전반에 관한 독점적 지식을 가지고 있었는데 2000년대 이후 음악의 디지털화, 개인 PC 보급 확대, 인터넷의 등장 등이 스튜디오를 더이상 필요하지 않게 만들었다. 즉 스튜디오가 가지고 있던 독점적인 지식인 제작, 마케팅, 판매를 스튜디오를 거치지 않고도 가능하게 된 것이다. 특히 냅스터라는 소프트웨어의 등장은 P2P 접근방식으로 MP3 파일을 공유하는 방식으로 음악을 소비하게 해주었기 때문에 스튜디오의 효용성이 더욱 떨어지게 되었다. 냅스터의 사례처럼 중개자 역할을 개인간의 상호작용으로 대체한다.는 아이디어를 기반으로 P2P 시스템이 음악 산업이 생태계 전쳬를 뒤흔든 것이다.

그런데 이런 P2P 시스템은 앞으로도 무형의 상품이나 디지털화된 상품 또는 서비스를 중개하는 역할을 주업으로 하는 산업에 큰 영향을 미치게 될 것이고, 결국은 이런 산업의 생태계도 P2P로 대체될 가능성이 높다. 금융업이 대표적인 예라고 할 수 있다. 대부분의 돈과 자산은 물리적인 지폐나 동전으로 거래되기 보다는 금융회사의 중앙 정보 기술 시스템에 저장되어 있고, 은행은 소비하는 사람들과 회사 사이의 중개자 역할을 주로 수행한다. 다만 간단한 거래 하나에도 상당수 많은 중개자가 관여하고 있어서 오랜 처리 시간과 많은 거래 비용을 필요로 하는 상황이다. 만약 P2P 거래 시스템을 사용한다면 훨씬 적은 시간과 비용을 들여 거래를 진행할 수 있다. 현재 은행의 중앙 통제 시스템에 비해 P2P 시스템은 중개자를 통해 간접적으로 상호작용하지 않고 거래 당사자끼리 직접 상호작용하기 때문에 처리 시간과 비용이 줄어든다.

P2P 시스템과 블록체인은 어떤 관련이 있나?

  1. P2P 시스템의 정의

여러 노드(개별 컴퓨터)들로 구성된 분산 소프트웨어 시스템. 한 노드의 자원을 다른 노드들이 직접 사용할 수 있다는 특징이 있다.

P2P 시스템에 참여하면 사용자의 컴퓨터는 시스템의 노드로 전환되고, 모든 노드에게는 동등한 권리와 역할이 주어진다. 즉 시스템의 모든 노드는 동일한 기능과 책임을 가지고, 모든 사용자의 컴퓨터는 자원의 공급자인 동시에 소비자가 된다.

  1. P2P 시스템의 아키텍처

P2P 시스템의 구조는 분산 컴퓨터 시스템

개별 노드로 구성된 컴퓨터들은 서로 자원을 공유한다. 순수 분산 P2P 시스템은 중앙에서 통제하거나 조정하는 어떤 요소도 없다. 따라서 모든 노드는 동일한 과제를 수행하고, 자원과 서비스의 생산자인 동시에 소비자 역할을 한다. 물론 중앙 통제 요소를 가지는 P2P 시스템도 존재한다. 이 시스템은 중앙 통제와 분산 시스템이 장점만을 뽑아 만들어진 것으로 중앙 노드를 이용해 노드 간 상호작용을 중개하고, 피어 노드가 제공하는 서비스 목록들을 유지 관리하고 노드를 검색하고 식별한다.

  1. P2P 시스템과 블록체인의 연관성

블록체인은 분산 시스템에서 무결성을 확보하고 유지하는 도구이다. 즉 순수 분산 P2P 시스템은 무결성의 확보와 유지를 위해 블록체인을 사용한다.

이러한 P2P 시스템과 블록체인의 연관성 때문에 블록체인이 중요한 요소가 된 것이고, 무결성을 유지하고 확보하기 위한 도구라는 점보다 더욱 중요한 점은 탈중개화이다. 블록체인은 단지 탈중개화를 위한 도구일 뿐이다.


References
[블록체인 무엇인가?] 다니엘 드레셔 지음

아이템 56 정보를 감추는 목적으로 private 사용하지 않기

자바스크립트는 클래스에 비공개 속성을 만들 수 없다. 비공개 속성임을 나태내기 위해 언더스코어(_)를 접두사로 붙이던 것이 관례로 인정되어 왔던 것 뿐이다. 하지만 언더스코어를 붙이는 것은 비공개라고 표시한 것 뿐, 일반적인 속성과 동일하게 클래스 외부로 공개되어 있다.

1
2
3
4
5
6
class Diary {
private secret = 'cheated on my English test'
}

const diary = new Diary()
;(diary as any).secret // OK

타입스크립트에서 위의 예시처럼 public, protected, private 접근 제어자를 사용하기 때문에 규칙을 강제하는 것으로 오해하기 쉬운데 이는 타입스크립트 키워드이기 때문에 컴파일 후에 제거된다. 그래서 타입스크립트의 컴파일되면 위의 예제는 아래의 예제처럼 자바스크립트 코드(target=ES2017)로 변환된다.

1
2
3
4
5
6
7
8
class Diary {
constructor() {
this.secret = 'cheated on my English test'
}
}

const diary = new Diary()
diary.secret

컴파일 후에 확인해보면 타입스크립트 키워드인 private이 제거되었고, secret은 일반적인 속성이어서 접근할 수 있다. 즉 언더스코어의 관례처럼 타입스크립트의 접근 제어자들도 런타임에는 아무런 효력이 없다. 심지어 타입스크립트 상태에서도 단언문을 사용하면 private에 접근 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
declare function hash(text: string): number

class PasswordChecker {
checkPassword: (password: string) => boolean
constructor(passwordHash: number) {
this.checkPassword = (password: string) => {
return hash(password) === passwordHash
}
}
}

const checker = new PasswordChecker(hash('s3cret'))
checker.checkPassword('s3cret') // Returns true

따라서 정보를 감추기 위한 목적으로 private을 사용하면 안된다. 자바스크립트에서 정보를 숨기기 위한 가장 효과적은 방법은 클로저이다. 위의 예시는 생성자에서 클로저를 만드는 예시이다. 이렇게 작성하면 PasswordChecker의 생성자 외부에서 passwordHash 변수에 접근할 수 없어서 정보를 숨기는 목적은 달성하게 되는데, 주의사항이 있다. passwordHash를 생성자 외부에서 접근할 수 없기 때문에 passwordHash에 접근해야 하는 메서드는 생성자 내부에 정의되어야 한다. 또 메서드 정의가 생성자 내부에 존재하게 되면, 인스턴스를 생성할 때마다 각 메서드의 복사본이 생성되기 때문에 메모리를 낭비하게 된다. 클로저를 쓰지 않으면 현재 표준화가 진행 중인 비공개 필드 기능을 사용할 수 있는데, 접두사 #을 붙여서 타입 체크과 런타임 모두에서 비공개로 만드는 역할을 한다.


아이템 57 소스맵을 사용하여 타입스크립트 디버깅하기

타입스크립트 코드를 실행한다는 것은 타입스크립트 컴파일러가 생성한 자바스크립트 코드를 생성한다는 것이다. 그런데 변환된 자바스크립트 코드는 복잡해서 디버깅하기가 어렵다. 디버깅하기 쉽도록 해결책을 내놓은 것이 소스맵이다. 소스맵은 변환된 코드의 위치와 심벌들을 원본 코드의 원래 위치와 심벌들로 매핑한다. 보통 자바스크립트로 변환된 코드는 원본 코드와 거의 비슷해서 디버깅하기 쉽지만 복잡하게 변환된다면 소스맵이 필요하다. 타입스크립트가 소스맵을 생성할 수 있도록 tsconfig.json에 소스맵 옵션을 다음과 같이 설정한다.

1
2
3
4
5
{
"compiletOptions": {
"sourceMap": true
}
}

아이템 58 모던 자바스크립트로 작성하기

타입스크립트 코드를 특정 버전의 자바스크립트로 컴파일 할 수 있다. 즉 타입스크립트를 자바스크립트 트랜스 파일러로 사용할 수 있다. 옛날 버전의 자바스크립트 코드를 타입스크립트 컴파일러에서 동작하게 만들면 그 이후 회신 버전의 자바스크립트 기능을 코드에 추가해도 문제가 없다. 이렇게 옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트로 바꾸는 작업이 타입스크립트로 전환하는 작업의 일부라 볼 수 있다. 타입스크립트는 자바스크립트의 상위 집합이기 때문에 코드를 최신 버전으로 바꾸다 보면 타입스크립트의 일부를 저절로 익힐 수 있다. 타입스크립트 도입 시 가장 중요하나 기능은 ECMAScript 모듈과 ES2015 클래스이다.

ECMA 모듈 사용하기

ES2015부터 import, export를 사용하는 ECMAScript 모듈이 표준이 되었다. 따라서 자바스크립트 코드가 단일 파일이거나 비표준 모듈 시스템을 사용 중이라면 ES 모듈로 전환해야 한다. 이 과정에서 웹팩이나 ts-node와 같은 도구가 필요할 수도 있다. ES 모듈 시스템은 모듈 단위로 전환할 수 있게 해주기 때문에 점진적 마이그레이션이 원활해진다.

프로토타입 대신 클래스 사용하기

자바스크립트에서 프로토타입 기반의 객체 모델을 사용하는데, 많은 개발자들이 클래스 기반 모델을 선호했기 때문에 ES2015에서 class 키워드를 사용하는 클래스 기반 모델이 도입되었다. 만약 단순한 객체를 다룰 때 프로토타입을 사용하고 있었다면 클래스로 바꾸는 것이 좋다.

var 대신 let/const 사용하기

var 키워드에는 스코프 규칙의 문제가 있었고, let이나 const를 쓰는 것으로 이러한 문제를 피할 수 있었다. var로 되어있던 코드를 let이나 const로 수정하면 일부 코드에서 타입스크립트가 오류를 표시할 수 있다. 만약 오류가 발생했다면 잠재적으로 스코프 문제가 존재하는 코드이니 수정이 필요하다.

for(;;) 대신 for-of 또는 배열 메서드 사용하기

과거에는 자바스크립트가 배열을 순회할 때 for 루프를 사용했는데 모던 자바스크립트에는 for~of가 존재하기 때문에 이를 사용하면 된다. for~of가 for문에 비해 코드가 짧고 인덱스 변수를 사용하지 않기 때문에 실수를 줄일 수 있다. 만약 인덱스 변수가 필요하다면 forEach를 사용하면 되고, 비슷한 루프문으로 for~in도 있지만 몇 가지 문제점이 있으므로 쓰지 않는 것이 좋다.

함수 표현식보다 화살표 함수 사용하기

this 키워드는 일반 변수들과 다른 스코프 규칙을 가지기 때문에 가장 어려운 개념 중 하나이다. 그러나 화살표 함수를 사용하면 상위 스코프의 this를 유지할 수 있다. 컴파일러 옵션에 noImplicitThis(또는 strict)를 설정하면 타입스크립트가 this 바인딩 관련된 오류를 표시해준다.

단축 객체 표현과 구조 분해 할당 사용하기

1
2
3
4
5
6
7
8
const x = 1,
y = 2,
z = 3
const pt = {
x: x,
y: y,
z: z
}

위와 같은 코드는 아래와 같이 구조 분해 할당을 이용하여 더 간단하게 표현할 수 있다.

1
2
3
4
const x = 1,
y = 2,
z = 3
const pt = { x, y, z }

함수 매개변수 기본값 사용하기

자바스크립트의 함수의 모든 매개변수는 선택적(생략 가능)이며, 매개변수를 지정하지 않았을 때는 undefined로 간주된다. 매개변수에 기본값을 직접 지정할 수도 있는데 이렇게 하면 코드가 간결해진다.

저수준 프로미스나 콜백 대신 async/await 사용하기

async/await가 코드의 가독성을 높이는 것 뿐 아니라 비동기 코드에 타입 정보가 전달되어 타입 추론을 가능하게 한다.

연관 배열에 객체 대신 Map과 Set 사용하기

타입스크립트에 use strict 넣지 않기

버그가 될 수 있는 코드 패턴에 오류를 표시해 주는 엄격 모드가 ES5에서 도입되었는데 타입스크립트에서 수행되는 안정성 검사가 엄격 모드보다 훨싼 더 엄격한 체크를 수행한다.


아이템 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기

@ts-check 지시자를 써서 타입 체커가 파일을 분석하고, 발견된 오류를 보고하도록 지시할 수 있다. 단 매우 느슨한 수준으로 타입 체크를 수행하고, noImplicitAny를 해제한 것보다 헐거운 체크를 수행한다. JSDoc의 @types 구문을 사용해서 타입 단언을 대체할 수 있는데 이때 중요한 것은 타입을 감싸는 중괄호가 필요하다는 것이다. 이처럼 자바스크립트 환경에서도 @ts-check 지시자와 JSDoc 주석으로 타입스크립트와 비슷한 경험의 작업이 가능하다.

아이템 60 allowJs로 타입스크립트와 자바스크립트 같이 사용하기

마이그레이션 기간 중에 자바스크립트와 타입스크립트가 동시에 동작할 수 있도록 해야 한다. 이 두 가지가 공존할 수 있도록 하는 핵심은 allowJs컴파일러 옵션이다. 타입스크립트 파일과 자바스크립트 파일을 서로 임포트할 수 있게 해준다. 타입 체크와 관련이 없지만 기존 빌드 과정에 타입스크립트 컴파일러를 추가하기 위해서도 allowJs 옵션이 필요하고, 모듈 단위로 타입스크립트로 전환하는 과정에서 테스트를 수행해야 하기 때문에도 이 옵션이 필요하다.


아이템 61 의존성 관계에 따라 모듈 단위로 전환하기

점진적 마이그레이션에서는 모듈 단위로 각개격파하는 것이 이상적인데, 한 모듈을 골라서 타입 정보를 추가하면 해당 모듈이 의존하는 모듈에서 비롯되는 타입 오류가 발생하게 된다. 따라서 다른 모듈에 의존하지 않는 최하단 모듈부터 시작하여 의존성의 최상단에 있는 모듈을 마지막으로 완성해야 한다.

  • 서드파티 라이브러리 타입 정보를 가장 먼저 해결한다.

프로젝트 내에 존재하는 모듈은 서드파티 라이브러리에 의존하지만 서드파티 라이브러리는 해당 모듈에 의존하지 않기 때문에 가장 먼저 타입 정보를 해결해야 한다. @types모듈을 설치하면 된다. (e.g. @types/lodash)

  • 외부 API의 타입 정보 추가

프로젝트 내의 모듈은 API에 의존하지만 API는 해당 모듈에 의존하지 않기 때문에 먼저 해결한다. 단 특별한 문맥이 없어서 타입스크립트가 추론하기 어렵기 때문에 API에 대한 사양을 기반으로 타입 정보를 생성하도록 한다.

  • 의존성 관계도에서 가장 의존성이 낮은 모듈부터 타입 정보 추가
    대부분의 프로젝트 최하단에 유틸리티 종류의 모듈이 위치하는 패턴이 있다. 이때 중요한 것은 타입정보를 추가한 것이지 리팩토링을 하려 해서는 안된다.

  • 선언되지 않은 클래스 멤버

자바스크립트에서 클래스 멤버 변수를 선언할 필요가 없지만 타입스크립트에서는 명시적으로 선언해야 한다.

  • 타입이 바뀌는 값

자바스크립트일 때 문제가 없다가 타입스크립트가 되는 순간 오류가 발생하는 값들이 많은데, 한꺼번에 객체를 생성하여 간단히 오류를 해결할 수 있다. 한꺼번에 생성이 어렵다면 타입 단언문을 사용할 수 있다. 단, 당장 마이그레이션이 최우선이라 임시 방편으로 사용한 것이지 마이그레이션이 완료된 후에는 적절한 방법으로 타입을 선언해야 한다. 또한 자바스크립트에서 JSDoc와 @ts-check를 사용해서 타입 정보를 추가했다면 타입스크립트로 전환하는 순간 타입 정보가 무효화 된다는 것에 유의하도록 한다.

  • 테스트 코드를 타입스크립트로 전환
    로직 코드가 테스트 코드에 의존하지 않기 때문에, 테스트 코드는 항상 의존성 관계도의 최상단에 위치한다. 따라서 최하단의 모듈부터 마이그레이션 하는 중에도 테스트는 정상적으로 수행할 수 있다.

아이템 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기

만약 noImplicitAny를 설정하지 않았다면 타입 선언에서 비롯되는 실제 오류를 완벽하게 잡을 수 없으므로 마이그레이션이 완료되었다고 말하기 어렵다. 아래와 같은 코드는 noImplicitAny:true를 설정한 순간 에러가 발생하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// tsConfig: {"noImplicitAny":true,"strictNullChecks":false}

// HIDE
class Chart {
indices: number[]
// END
getRanges() {
for (const r of this.indices) {
const low = r[0]
// ~~~~ Element implicitly has an 'any' type because
// type 'Number' has no index signature
const high = r[1]
// ~~~~ Element implicitly has an 'any' type because
// type 'Number' has no index signature
// ...
}
}
// HIDE
}
// END

noImplicitAny를 설정할 때는 로컬에만 설정하고 작업을 하여 점진적 마이그레이션이 가능하도록 한다.


References
[이펙티브 타입스크립트] 댄 밴더캄 지음