블록체인은 전세계적으로 분산되어있는 노드들간에 합의를 이루는 과정을 통해서 신뢰를 확보하는데요.  이때 처음 내 컴퓨터에 있는 이더리움 프로그램이 켜질때 어떻게 전세계의 컴퓨터들과 연결되는 걸까요?그것에 관련된 글을 적어 보았습니다.

(이 글은 의문을 품고 공부하면서 동시에 작성되어 두서가 없습니다. 또한 잘못된 정보(혹은 업데이트 되지 않은 정보)가 포함 되어 있을 가능성도 있음을 알려 드립니다.)

이더리움에서 흩어져 있는 노드를 찾을 때 Kademlia DHT 의 일부를 수정해서 사용한다고 하는 글을 보았을 때 좀 이해가 안 갔는데, 이유는 Kademlia은 토렌토등에서 사용되며 특정 노드/ 특정 값을 찾고, 일부 노드에만 분산 저장하기 위한 알고리즘인데, 이더리움도 비트코인처럼 그저 랜덤 하게 설정되는것으로 추측 했었기 때문이다. 특정 노드 / 특정 값을 찾기 위함이라면 이더리움 스웜 (IPFS 와 비슷하며, IPFS 는 그 용도로 이미 Kademlia 를 사용중) 이 말이 되지, 일반 이더리움에서는 어떻게 적용 된다는 말인지 궁금했다. 따라서 좀 더 정확히 알아 보기로 했다.

(비트코인의 경우 마스터링 비트코인에는 '종자노드' 로부터 새로운 노드들의 정보를 얻고, 그 노드들과 연결된 노드로 전파되어 내 노드 정보를 전파하는 동시에 , 연결에 연결된 노드들의 정보를 알 수 있다. 정도로만 나와서 그 많은 노드 정보 중에 선택되는 노드의 기준이 무엇인지 그 책 만으로는 알 수가 없다. 따라서 랜덤으로 생각하고 넘겼는데..이 글을 쓰면서 찾아보니 진짜 랜덤이네. 그렇다면 Kademlia 의 자신과 가까운 녀석들의 정보를 많이 가지고 있게 하는 것이, 적은 노력으로 골고루 분포 될 수 있게 하는 힘이 되지 않을까 싶었다.) 

먼저 이더리움에서 사용되고 있다는 카뎀리아에 대해서 알아보자.

Kademlia 

Kademlia 는 아래와 같은 4개의 프로토콜이 있다.

  • PING — 상대 노드가 여전히 살아있는지 확인
  • STORE — 노드에 (키,값) 쌍을 저장
  • FIND_NODE — 자신의 버킷에 있는 k개의 노드들 (요청된 노드에 가장 가까운)들을 리턴 한다. 
  • FIND_VALUE — FIND_NODE 와 동작방식이 같으나, 해당 키가 노드ID가 아니라 저장소에 있다면, 해당되는 값을 리턴 해준다. 


FIND_VALUE 와 STORE 는 데이터 분산 저장에 관련된 내용이니 분명히 이더리움에서는 (스웜에서는 사용되겠지만) 제외하고,  FIND_NODE 와 PING 만 노드 디스커버리를 위해 사용되리라 추측 할 수 있겠다.

이에 따라서 Kademlia 에서 사용하는 노드 디스커버리에 한정 해서 실생활 상황으로 설명 해 보겠다. , 

제주도 서귀포 출신 A군이, 광화문 교보문고를 찾아 가려면, 일단 서울역 가는 방법만 알면되고, 그 후에 서울역에서 광화문 가는 방법을 물어보면 될 것이다.  광화문에 가서 그 근처 사람에게 물어보면 교보문고를 찾을 수 있을 것인데, 그 근처의 지리는 그 근처의 사람이 더 잘 안다는 매우 단순한 논리에서 시작된다. 

다시 반복하자면 제주도 서귀포 출신 A 군은 서귀포 근처(거리 상 가까운 지리)에 대해서 잘아는 친구들이 서귀포 각 지역마다 있을 것이다. 서울,대구,부산,광주에 대해서는 그 넓은 지역의 친구 한 명 만 알면 된다. A군이 서울의 신촌의 특정 맛집을 찾는 방식은 서울 친구에게 물어보면 그 서울 친구는 자기 주변의 친구 중에 신촌 근처에 사는 친구의 전화번호를 A군에게 알려주면 될 것이다. 신촌 근처사는 B군이 제주도 특정 맛집을 찾을 때도 마찬가지다. 먼저 아까 교환한 전화번호로 A군에게 전화하면 A군은 그 지역 제주도 친구의 전화번호를 건네 주면 될 것이다. 

각 노드가 모든 노드를 알 필요 없이 자기와 가까운 노드에 대해서는 세밀히, 먼 노드에 대해서는 대표격으로 몇개만 안다면, 몇 단계만 거치면 서로서로 찾아 갈 수 있다는 것이며, Kademlia 의 주요 특징은 그 거리를 XOR 연산으로 한다는 것이다. (즉 지리적인 토폴로지로 가까운 노드를 찾는 것이 아님. 이 부분은 문제가 될 소지가 있어 보임. 한국에서 미국 노드와 연결되 있으면 아무래도 추가 부담이 들어가니까)

즉 노드A의 ID 가 1111 이고 노드 B의 ID가 0111 이고 노드 C의 ID가 1110 이면 A와 B의 거리는 굉장히 먼것이고, A와 C의 거리는 매우 가까운 것으로 치고, 자기와 가까운 거리의 노드의 정보에 대해서만 많이 가지고 있고, 멀리 떨어진 정보는 대표노드 한두개씩만 가지고 있는 방식이다.  log2 n 쿼리만으로도 모두(아무것이나) 찾을 수 있다.위 그림에서 빨강색 노드인 0011 이 1100 노드를 찾기 위해서는 1 로 시작되는 모든 노드 정보를 알 필요 없이 1로 시작되는 노드 하나 만 알면되고, 그 노드를 통해 1100의 행방을 수소문 하면 되는 것이다. 결론적으로 Kademlia 는 모든 정보를 가지고 있을 수 없는 거대한 집합군에서 특정 노드를 빠르게( O(log2(n)) 찾기 위한 방식인 것이다. 


이더리움에서 노드 디스커버리?

일단 이더리움에서 Kademlia 를 사용한다는 것은 잊어버리고, 어떻게 내 컴퓨터에서 이더리움을 시작하면 ,연결되어야 할 노드들을 알 수 있는지  알아보자. 

먼저 구글링을 통해 스택오버플로우의 https://ethereum.stackexchange.com/questions/7743/what-are-the-peer-discovery-mechanisms-involved-in-ethereum  답변을 발견했는데, 아래의 순서로 p2p를 시작한단다.

1. 부트스트랩 노드에 대해 이미 하드코딩되어 알고 있다. (부트스트랩 노드는 항상 활성화 되어 있다고 가정한다)
2. 부트스트랩 노드는 얼만큼의 시간 안 에 그들과 접속한 모든 노드(즉 최신의)들의 리스트를 가지고 있다. 
3. 내 노드가 이더리움 네트워크에 접속 할 때 부트스트랩 노드에 먼저 접속해서 최신의 노드들에 대한 정보를 공유한다. (내가 접속하는 순간 나도 그 리스트에 들어가며, 남들이 나에게 연결 할 수 있게 될 것이다)
4. 부트스트랩과는 빠이빠이하고, 정해진 숫자의 공유된 노드 리스트에게 연결한다.

일단 이 답변에서는 Kademlia의 역할이 확실히 보이지는 않는다. 항상 부트스트랩 노드에 의지해서 연결 될 노드를 찾지는 아닐 것이며, 자신의 노드에 예전에 연결된 노드들이 있고 그 노드들의 상태를 확인해서 문제 없으면 다시 연결 할 것이고, 모든 노드에 연결이 안되면, 그때 부트스트랩 전용 노드를 통해 다시디스커버리를 할 것으로 추측 할 수 있다.기존에 가지고 있던 노드들 중 하나만 접속된 다면 이 녀석에게 노드 디스커버리 프로토콜을 적용 해서 추가 노드를 찾을 것이다. 모든 노드가 라우팅테이블을 가지고 있기에.

일단 이 정도 추론해보고, 실제 이더리움에서는 어떻게 구성이 되어있고, 활용되는지 관련 문서를 번역해보고, 소스도 살펴 보았다.

Network Formation 

  • 새로운 노드는 신뢰 할 만하게 접속 해야할 노드를 찾을 수 있다.
  • 노드들은 다른 노드들과 균일하게 접속하기 위해 충분한 네트워크 토폴로지를 갖는다.
  • 노드 구분자는 랜덤이다.

RLPx 는 p2p 이웃노드탐색프로토콜로써 사용된 Kademlia 식의 라우팅을 이용한다. RLPx 탐색은 XOR를 위해 512-bit 공개키를 이용하는데  DHT 전체 특징은 구현되지 않았다.  나의 노드가 인터넷에 직접 연결되어 있거나, UPnP 옵션이 있다면 연결 요청을 받을 수도 있을 것이다.

Implementation Overview 

패킷들은 동적으로 만들어지고, RLP 엔코딩된 헤더가 앞에 붙으며, 암호화되고, 인증된다. 멀티플렉싱이 패킷의 목적지 프로토콜을 명시한 프레임 헤더를 경유해 작동 된다.  모든 암호계산은 secp256k1로 하며, 각 노드는 정적 개인키를 유지하여야 한다.

RLPx 구현은 다음과 같이 구성된다:

  • 노드 디스커버리 <-- 이번 포스트에서는 이것만 집중 
  • 암호화된 전송 
  • 프레이밍(Framing)
  • 흐름 컨트롤

Node Discovery  

노드 디스커버리와 네트워크 형식은 Kademlia-like UDP 식으로 구현되어 있다. Kademlia 와의 주요 차이점은 

  • 패킷들은 서명된다.
  • 노드 아이디는 공개키이다.
  • DHT 관련 몇몇 특징이 제외 되었다. (FIND_VALUE 와 STORE)
  • XOR 거리는 sha3(nodeid)에 기반하여 계산된다.

노드 아이덴터티 

이더리움 각 노드는 공개키(256-bit hash (sha3/Keccak-256)는 해당 노드의 ID 로 사용된다. 모든 노드의 이진 배열 길이는 동일하기에 XOR로 길이를 구하는 토대가 된다. 두 노드간의 거리는 아래와 같다.

istance(n₁, n₂) = keccak256(n₁) XOR keccak256(n₂)

노드 테이블 (* 이더리움 특화 말고 일반적인 설명)

각 노드는 자신의 이웃에 해당 하는 다른 노드들에 대한 정보를 유지하고 있다. 모든 노드는 라우팅 역할을 한다고 볼 수 있겠다.이 이웃노드들은 'k-bucket' 이라고 하는 라우팅 테이블을 보유하고 있는데 구조는 다음과 같다.

- 하나의 버킷은  2^i ~2^(i+1)  사이의 값을 갖는 값들로 이루어져 있으며, (0 ≤ i < 256 ) 개의 버킷가 있다.
- 자신의 노드와 다른 노드의 거리 값을 구해서 그 값에 해당 하는 값이 6이면,  2^2~ 2^3 사이의 버킷에 들어간다.
- 각 버킷에는 k 개의 노드 정보(ip,port,nodeid)들을 가지고 있다.( k 는 redundancy 라고 하며 이더리움에서는 k = 16) , 즉 2^10~ 2^(11) 사이의 값도 16개를, 2^100~ 2^(101) 사이의 값 중에서도 16개를 가지고 있으니, 자신과 가까운 노드들일 수록 자세하게 가지고 있는게 확인 된다. 이것은 글 초기의 제주도 서귀포 출신 A군의 이야기를 다시 생각해 보면 쉽게 이해 할 수 있을 것이다. 

* 위의 이미지(설명)는 이더리움에서 사용되는 노드테이블 구조와 정확히 매칭되진 않는다. 이더리움은 조금 변경하였다. 실제 코드에는 아래와 같이 설정되어있다.

   bucketSize      = 16 // 하나의 버켓에 담기게 될 노드 숫자.

// We keep buckets for the upper 1/15 of distances because
        // it's very unlikely we'll ever encounter a node that's closer.

hashBits          = len(common.Hash{}) * 8
        nBuckets         = hashBits / 15       // 버킷의 갯수 


테이블 안의 노드 정보는 아래와 같이 업데이트 된다.

새로운 노드 (N1)를 만날 때 마다, 대응하는 버킷에 삽입 될 수 있다. 버킷이 k 개 미만의 엔트리를 포함하면, N1은 그냥 첫번째 엔트리에 추가 되며, 버킷에 이미 k 개 항목이 포함 되어 있으면 버킷의 가장 최근에 본 노드 N2에 ping 패킷을 보내서 유효성을 다시 체크하고 N2에서 응답을 받지 못하면 죽은 것으로 간주헤 제거하고 N1이 버킷의 첫번째 엔트리에 추가한다. 

노드 찾기 및 라우팅 

특정 노드ID에서 가장 가까운 K (k closest nodes) 개의 노드의 위치를 찾아(lookup)보자. 참고로 여기서 k 는 위의 k-bucket 의 k (k=16개였던)와 의미가 다르다. 

첫째. 보통 타겟에 가장 가까운  3개의 Node ID 를 찾는다.
둘째. 그 노드들에게 동시적으로 FindNode RPC 콜을 날려준다. 
세째. 그 노드들은 가지고 있는 노드 정보 중에 타겟에 가까운 노드를 대답해준다.
네째. 그 노드들로 부터 응답받은 노드를 k-bucket에 담고, 다시 FindNode RPC 콜을 날려준다. 재귀적으로 시행한다.(보통 Kademlia 에서는 이런 행동을 하는 도중에 모든 노드들이 새로운 노드로 노드 테이블을 업데이트 하는데, 이더리움에서도 마찬가지일 것이다. 즉 live node 를 업데이트하는 과정이 find node 과정중에 자연스럽게 발생된다.)
다섯째. 더 이상 가까운 노드를 리턴하지 않으면 종료한다. 

이것 역시 이해가 안 간다면,  글 초기의 제주도 서귀포 출신 A군의 이야기를다시 생각해 보면 쉽게 이해 할 수 있으리라 본다.

연결 결정/유지

RLPx 는 노드간의 거리 기반으로 'potential' 노드 리스트를 제공한다. 이상적인 피어 숫자(디폴트 5) 에 준하여 잘 작동하는 접속만 유지하려 노력한다.이 전략은 모든 연결되었던 'close' 노드에 대해여 1개의 랜덤한 노드와 접속하는 것에 의해 실현된다. 연결을 결정하기 위한 자신만의 메타데이터와 전략을 사용 할 수 도 있다. 

주요 코드 (V4)

golang으로 작성된 코드는 (여기) 에 있으며,  cpp 등으로 만들어진 것도 있다. 노드디스커버리에 관련된 소스는 p2p 폴더안의 discover폴더가 버전4, discv5폴더가 버전5를 다루고 있다.p2p 폴더의 server.go에서 시작된 로직은 

- table.go 에는 전반적인 kademlia 알고리즘을 관리하고 있으며,
- node.go 에는 kademlia 에서 사용되는 노드 구조체를
- udp.go 에는 kademlia 알고리즘에서 UDP 프로토콜을 사용하여 외부로 통신하는 부분을 담고 있다.
즉 table.go 의 lookup 메소드는 내부에 findnode 메소드를 가지고 있는데 , findnode 메소드는 udp 콜을 한다. 
- database.go 에는 node 관련된 정보를 저장한 level.db 와의 통신부분을 담당하며
- ntp.go 에는 network time protocol 의 약자로 이더리움 피어들끼리 현재 시간을 맞추기 위한 로직을 담는다. 

부트스트랩 노드 정보 : (소스링크)


var MainnetBootnodes = []string{

	"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", // IE
	"enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303",  // US-WEST
	"enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303", // BR
	"enode://158f8aab45f6d19c6cbf4a089c2670541a8da11978a2f90dbf6a502a4a3bab80d288afdbeb7ec0ef6d92de563767f3b1ea9e8e334ca711e9f8e2df5a0385e8e6@13.75.154.138:30303", // AU
	"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",  // SG

	// Ethereum Foundation Cpp Bootnodes
	"enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303", // DE

}

노드디스커버리 관련 기본 구조체 : (소스링크)

type (
	ping struct {
		Version    uint
		From, To   rpcEndpoint
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// pong is the reply to ping.
	pong struct {
		// This field should mirror the UDP envelope address
		// of the ping packet, which provides a way to discover the
		// the external address (after NAT).
		To rpcEndpoint

		ReplyTok   []byte // This contains the hash of the ping packet.
		Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// findnode is a query for nodes close to the given target.
	findnode struct {
		Target     NodeID // doesn't need to be an actual public key
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// reply to findnode
	neighbors struct {
		Nodes      []rpcNode
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}

	rpcNode struct {
		IP  net.IP // len 4 for IPv4 or 16 for IPv6
		UDP uint16 // for discovery protocol
		TCP uint16 // for RLPx protocol
		ID  NodeID
	}

	rpcEndpoint struct {
		IP  net.IP // len 4 for IPv4 or 16 for IPv6
		UDP uint16 // for discovery protocol
		TCP uint16 // for RLPx protocol
	}
)


FIND_NODE 함수:  (소스링크)

// findnode sends a findnode request to the given node and waits until
// the node has sent up to k neighbors.
func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
	nodes := make([]*Node, 0, bucketSize)
	nreceived := 0
	errc := t.pending(toid, neighborsPacket, func(r interface{}) bool {
		reply := r.(*neighbors)
		for _, rn := range reply.Nodes {
			nreceived++
			n, err := t.nodeFromRPC(toaddr, rn)
			if err != nil {
				log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
				continue
			}
			nodes = append(nodes, n)
		}
		return nreceived >= bucketSize // bucketSize 는 16이고 table.go 파일에 하드코딩 되어 있다.
	})
	t.send(toaddr, findnodePacket, &findnode{
		Target:     target,
		Expiration: uint64(time.Now().Add(expiration).Unix()),
	})
	err := <-errc
	return nodes, err
}
// pending adds a reply callback to the pending reply queue.
// see the documentation of type pending for a detailed explanation.
func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <-chan error {
	ch := make(chan error, 1)
	p := &pending{from: id, ptype: ptype, callback: callback, errc: ch}
	select {
	case t.addpending <- p:
		// loop will handle it
	case <-t.closing:
		ch <- errClosed
	}
	return ch
}

FIND_NODE 패킷 처리 함수(Neighbors 데이터 공유):  (소스링크)

func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
	if expired(req.Expiration) {
		return errExpired
	}
	if !t.db.hasBond(fromID) {
		return errUnknownNode
	}
	target := crypto.Keccak256Hash(req.Target[:])
	t.mutex.Lock()
	closest := t.closest(target, bucketSize).entries
	t.mutex.Unlock()

	p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
	var sent bool
	// Send neighbors in chunks with at most maxNeighbors per packet
	// to stay below the 1280 byte limit.
	for _, n := range closest {
		if netutil.CheckRelayIP(from.IP, n.IP) == nil {
			p.Nodes = append(p.Nodes, nodeToRPC(n))
		}
		if len(p.Nodes) == maxNeighbors {
			t.send(from, neighborsPacket, &p)
			p.Nodes = p.Nodes[:0]
			sent = true
		}
	}
	if len(p.Nodes) > 0 || !sent {
		t.send(from, neighborsPacket, &p)  
	}
	return nil
}

UDP 객체의 send 메소드:  (소스링크)

func (t *udp) send(toaddr *net.UDPAddr, ptype byte, req packet) ([]byte, error) {
	packet, hash, err := encodePacket(t.priv, ptype, req)
	if err != nil {
		return hash, err
	}
	return hash, t.write(toaddr, req.name(), packet)
}

노드디스커버리(lookup):  (소스링크)

func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
var (
target = crypto.Keccak256Hash(targetID[:])
asked = make(map[NodeID]bool)
seen = make(map[NodeID]bool)
reply = make(chan []*Node, alpha)
pendingQueries = 0
result *nodesByDistance
)
// don't query further if we hit ourself.
// unlikely to happen often in practice.
asked[tab.self.ID] = true

for {
tab.mutex.Lock()
// generate initial result set
result = tab.closest(target, bucketSize)
tab.mutex.Unlock()
if len(result.entries) > 0 || !refreshIfEmpty {
break
}
// The result set is empty, all nodes were dropped, refresh.
// We actually wait for the refresh to complete here. The very
// first query will hit this case and run the bootstrapping
// logic.
<-tab.refresh()
refreshIfEmpty = false
}

for {
// ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
n := result.entries[i]
if !asked[n.ID] {
asked[n.ID] = true
pendingQueries++
go func() {
// Find potential neighbors to bond with
r, err := tab.net.findnode(n.ID, n.addr(), targetID)
if err != nil {
// Bump the failure counter to detect and evacuate non-bonded entries
fails := tab.db.findFails(n.ID) + 1
tab.db.updateFindFails(n.ID, fails)
log.Trace("Bumping findnode failure counter", "id", n.ID, "failcount", fails)

if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
tab.delete(n)
}
}
reply <- tab.bondall(r)
}()
}
}
if pendingQueries == 0 {
// we have asked all closest nodes, stop the search
break
}
// wait for the next reply
for _, n := range <-reply {
if n != nil && !seen[n.ID] {
seen[n.ID] = true
result.push(n, bucketSize) // 새로 얻는 노드를 통해서 다시 찾기 위해 result 에 푸시
}
}
pendingQueries--
}
return result.entries
}

결론

이더리움의 node discovery 는  비트코인에 비해 적은 노력으로 최신의 상태를 잘 반영 할 수 있으며, 보다 더 신뢰가능하며,고른 분포를 가진 망을 구성 할 수 있으리라 보인다. 

구체적인 노드디스커버리의작동 과정은 일단 이더리움 자체에 하드코드 되거나 자신이 입력한 seed node 로 부터 시작되는데, table.go 파일에 구현되어 있는 무한loop가 doRefresh 함수를 호출하여  항상 새로운 이웃노드들을 자신의 테이블에 저장하고 상태체크를 하는데, 이웃노드 탐색에는 lookup 함수가 사용되어 FIND_NODE 프로토콜을 이용하여 재귀적으로 이웃노드들에 대한 정보를 찾는다. 이 과정에서 자신의 테이블에 자신과 가까운 녀석들은 많이 담게 되고, 자신도 다른 노드들의 탐색과정을 도와야 하기 때문에 랜덤한 Node ID로 부터의 탐색도 하여 테이블 (K-BUCKET)에 다양한 노드를 담을 수 있게 된다. 

이렇게 라우팅테이블에 노드들이 담기게 되며, 계속해서 살아있는 노드들로 업데이트 된다. 
실제 데이터를 주고 받는 WIRE 프로토콜에서는 이렇게 노드디스커버리에 의해 연결 가능한 노드들만으로 이루어진  라우팅테이블에 담긴 노드들중 랜덤하게 선택 하여 실제 스트림 통신을 하게 된다.

* 이더리움  RLPx 에 대한 구체적인 코드 분석(GO,C++,RUST) 및 비트코인/Hyperledger 와의 비교 포스트를 따로 계속 연재 할 예정이다.참고로 pydevp2p 라는 파이썬 기반의 오픈소스로 먼저 코드리딩을 하는게 훨씬 편할 것이다.
  • Node Discovery and Network Formation
  • Peer Preference Strategies
  • Peer Reputation
  • Multiple protocols
  • Encrypted handshake
  • Encrypted transport
  • Dynamically framed transport
  • Fair queuing

레퍼런스:

https://rchain.atlassian.net/wiki/spaces/CORE/pages/15564804/Ethereum+P2P+Node+Discovery+and+Routing
https://github.com/ethereum/devp2p/blob/master/discv4.md
https://github.com/ethereum/devp2p/blob/master/rlpx.md
https://eprint.iacr.org/2015/263.pdf



블록체인과 TPS

Transactions Per Second (TPS) 란 말 그대로 초당 트랜잭션 수를 의미하는데, 시중 대형은행의 TPS 는 보통 몇백에서 몇천건이라고 합니다. 글 본론에서 소개해드릴 자바기반의 LMAX 아키텍쳐에서 사용한 DISRUPTOR 패턴은 초당 600백만 트랜잭션을 처리 할 수 있다고 하는데요. (실..실화냐?)

* DISRUPTOR 패턴을 한마디로 축약하면  "싱글 쓰레드" 의 힘!! 입니다.

TPS 는 보통 "- 외부에서 데이터를 받고 - 그 데이터를 처리하고 - 처리된 데이터를 외부로 내보내고" 를 한 싸이클로 보고 계산을 하게 되는데, 그림에서 보다시피, 외부에서 데이터를 받는 네트워킹 상황에 따라서도 영향을 받을 수 있으며, 내부의 로직을 처리하는 시간에 따라서도 영향을 받게 됩니다.  1세대 블록체인 기술인 비트코인 같이 모든 노드가 네트워킹에 참여하여 동일한 역할을 수행하는 경우, 그 부분에서의 제약이 작지 않으리라는 것을 추측 할 수 있을 것입니다.

또한 블록체인에서는 "신뢰의 속도"라는 개념이 TPS에 추가됩니다. 따라서 기존 메세지큐(MQ) 들의 TPS 를 계산하는 방식을 그대로 적용할 수는 없는 노릇이기도 합니다.


비트코인

비트코인 경우 1MB 의 블록용량한도와 10분의 블록 생성을 근거로, 1 트랜잭션을 계산해보면 약 7TPS 가 채 안나오게 됩니다. 네트워킹 능력을 떠나서 애초에 로직상에서 처리 할 수 있는 한도를 정해놨기 때문에 추가 성능향상은 트랜잭션 크기를 줄이거나, 블록생성 속도를 빠르게 할 수 밖에 없지요. (이런 논의는 계속되고 있습니다.) 즉 컴퓨터 공학적인 어떤 트릭이나 기술로만은 해결하기 힘든 문제입니다. (사이드 체인은 논외)

이더리움

이더리움 경우는 블록생성 주기가 대략 12초이며, TPS 는 10~30정도로 보고 있습니다. 일단 이더리움은 비트코인과 다르게 블록사이즈의 limit 와 트랜잭션 사이즈가 정해져 있지 않으며, 비트코인보다는 채굴속도도 비약적으로 빠르지만 TPS 처리가 쩌리인 이유는 블록당 사용할 수 있는 가스가 정해져 있기 때문입니다. 가스라는 것은 이더리움상에서의 코인을 좀 더 유연하게 처리하기 위한 대체재인데, 트랜잭션 비용을 처리 할 때 매개체로 사용됩니다. 그 가스량이 정해져 있다는 것은 블록사이즈의 크기가 정해져 있다는 것과 비슷한 제약을 가져오는 것이지요. (DDOS 등 외부 침입자의 무차별적인 트랜잭션 전송을 막기 위한 방편으로 트랜잭션별,블록별가스리밋이 생김) 

이더리움의 차세대 합의 메커니즘인 캐스퍼에서는 일단 블록생성 주기는 엄청 짧아 질 것이며, (신뢰의속도 증가) 기타 다양한 (샤딩 등) 기술들의 도입으로 TPS 는 훨씬 빨라지리라 예상되고 있습니다. 

하이퍼레저 패브릭

프라이빗 블록체인인 이 놈은 15노드로 10만 TPS 를 목표로 합니다.

스팀/EOS

이더리움의 강력한 라이벌인 EOS 는 백서에서 밝히길,싱글 쓰레드에서 1만 TPS를, 멀티쓰레드에서 100만 목표로 하고 있습니다. EOS 를 만든 댄라이머는 비트쉐어와 스팀도 만든 사람인데, 이것들은 모두 DPOS (간접민주주의 방식)라는 합의메커니즘을 사용하며, 플랫폼 내부에 있는 그래핀이라는 코어의 트랜잭션 처리 기술이 Distruptor 패턴에서 영감을 받았습니다. 어떤 엄청난 신뢰의 산물을(신뢰비용)을 만들기 위해 많은 시간을 쏟아 붙는 것도 아니고, 전국민이 투표하는것도 아니고, 몇몇 대표자들끼리 투표하는데 얼마 걸리겠습니까? 

(참고로 스팀/EOS 는 트랜잭션 비용도 무료(?)입니다. 소액결제부분에서 이더리움이 너무나 큰 약점으로 생각되는 부분이죠. 물론 엄청나게 뛰어나보이는 기술/철학 뒤의 약점도 많습니다. 참고. 트레이드오프가 있지 항상 참인 기술은 없으니까요, 블록체인 플랫폼간의 트랜잭션 비용 전쟁은 사용자들이 가장 흥미롭게 지켜 볼 만한 요소라고 생각합니다. 여담으로 저는 현재 이더리움의 사이드체인 기술들, 예를들어 loom network 와 스팀의 SMT에 굉장히 매력을 느끼며 저울질 중에 있습니다)

(https://steemit.com/coinkorea/@dev1by0/3qa1pb-eos 발췌)


이제 블록체인 얘기는 그만하고, EOS가 영향받은 LMAX Architecture 와 Distruptor 패턴에 대해서 살짝 알아 보겠습니다. 일단 이 패턴은 일반적으로 사용될 수 있는 기술이고, 오픈소스로 공개되어 있기 때문에, 여러분의 상황에 맞다면 가져다 쓸만하다고 볼 수 있습니다. 물론 그 상황이 초당 몇백,몇천 정도 TPS 수준으로 충분하다면 굳이 바꿀 필요는 없겠지요.이 놈은 초당 몇백만TPS를 처리하는 녀석이니까요. 


LMAX Architecture 와 Distruptor 패턴

제가 C++로 유체역학 솔루션을 만들던 (즉 대규모 트랜잭션처리에 대해선 무지) 2011년에 자바진영에서 엄청난 물건이 나왔습니다. 스타워즈에 나오는 광선총의 이름과 같은 Distruptor 패턴인데, smalllake 님의 블로그에 소개된 내용을 보면

"Java로 개발된 프로그램에 주는 Duke’s Choice Award를 2011년에 수상한 Distruptor는 현재 영국 LMAX에서 사용하고 있습니다. LMAX는 FX(외환거래)를 하는 곳입니다. 겉으로 보면 증권사같습니다만 FX는 중앙거래소가 없기때문에 LMAX자체가 거래소라고 할 수 있다고 합니다. 고객의 호가주문을 받아서 매매체결하는 부분으로 연결시켜주기 위한 라이브러리가 Disruptor입니다. Inter-Thread Library이며 고성능지지연(Low Latency)을 가능하도록 합니다. 그런데 LMAX는 이를 오픈소스로 공개하였습니다." 

라고 적혀져 있는데요. 해당 소스는 여기를 참고하시면 됩니다 (https://github.com/LMAX-Exchange/disruptor)
처음은 자바로 짜여져있으나, 모든 패턴이 그러하듯이 다른 언어로도 충분히 구현이 가능합니다. 이 패턴에는 어떤 외부 라이브러리가 필요 없으며, 자바의 고유 사용패턴을 함께 사용할 필요도 없습니다.

이 패턴을 개발하기전에 액터패턴 같은 것을 처음 시도하였지만, 쓰레드 사용에 대한 오류와 복잡도에 대한 부분은 줄였지만, 역시 액터사이에 메세지를 전달하기 위해 사용되는 큐에 대한 지연 부분이 문제가 되었다고 합니다. 즉 로직에서 지연보다 큐에서 블럭되어 있는 시간이 훨씬 길었다는 것이지요. 

이때 눈여겨 본 부분은 바로, "캐시" 이며, "캐시" 를 적극적으로 사용하기 위한 아이디어로 Distruptor는 탄생되었습니다.이것은 아무리 소프트웨어,특정 언어라고 말해도, 기본적인 하드웨어에 대한 관심도 중요하다는 교훈(mechanical-sympathy)을 줍니다. 


캐시

컴퓨터 내부의 각 장치들 간의 속도를 보면

보시다시피 , CPU는 1ns 즉 10억분의 1초의 속도를 자랑하지만,  HDD로 갈수록 점점 엄청 느려지는 것을 알 수 있습니다.

멀티코어 CPU라고 모든 캐시와 메모리를 각자 사용하는 것이아니라, 메모리는 물론 캐쉬도 공유합니다.

보통 우리가 (제가) 어떤 메세지를 전달하는 구조를 생각할때 가장 먼저 떠올리는 것은 생산자-소비자 패턴에 중간의 매개체로 언어에서 제공해주는 쓰레드 안전한 큐를 사용하는 생각을 했을 것인데요. 이런 큐는 항상 앞이나 뒤에서 머무르는 시간이 대부분일테고 (밸런스가 딱 맞기 힘들죠), 그리고 생산자들과 소비자들 쓰레드간에 Lock을 반드시 걸어줘야 할 겁니다. 이 Lock은 생각보다 훨씬 더 속도에 악영향을 미치는데요. 

락이 걸리는 순간 컨텍스트 스위칭이 커널에서 발생하며, 프로세서는 캐쉬안에 저장되있는 데이터를 잃어버리기 쉬운 환경이 조성됩니다. 캐시를 보다 잘 보존하기 위해서는 단순합니다. 그저 하나의 코어만 쓰기를 하면 됩니다. (다중읽기는 괜찮지만요) 만약 두개의 쓰레드가 각기 다른 값에 쓰기를 할지라도 서로 다른 캐시라인을 무효화 할 수 있게 됩니다. 이 말은 큐에서 하나는 head 에서 작업하고 하나는 tail에서 작업한다고 할 경우에도 캐시에 문제가 생길 수 있다는 의미입니다.


링버퍼 와 Distruptor 

그럼 Distruptor에서는 락을 걸지 않고 어떻게 쓰레드간에 데이터를 주고 받을 수있을까요? 그 키는 링버퍼에 있습니다.

보다시피, 버퍼의 개수는 정해져있으며, 마지막 슬롯이라는 개념이 없습니다. 위 그림상에서 7번이 끝이아니라, 0번버퍼를 다시 8번으로 그래도 재활용(재할당없이)하기 때문입니다. 즉 버퍼를 가르키는 포인터에 대한 인덱스는 버퍼사이즈만큼으로 정해져 있지만, 시퀀스는 무한히 (64 bit signed numbers: 초당 백만건 처리해도 3십만년) 생성됩니다. 

하나의 생산자와 하나의 소비자를 대상으로 할 경우 링버퍼에서 일어나는 일은 매우 단순한데요. 생산자와 소비자는 각각 자신의 어디쯤에 있는지만 서로 확인 할 수 있으면, 그 범위 내에서 작업을 하면 되기 때문입니다. 

예를들어 생성자가 5번까지 입력했어. 라고 하면 (cursor 위치가 5가 됨) 소비자는 5까지 읽어가면 됩니다. 
반대로 생성자는 소비자가 읽은 곳을 넘어가서 입력하면 안되겠지요. (waiting 이 이루어 집니다) 


생산자가 여러개 일 경우는 좀 특별하다. 생산자A가 10번 슬롯을 먼저 처리했다고해서, 그 곳까지 소비자가 읽을 순 없게 해야한다. 이유는 또다른 생산자B는 아직 8번슬롯을 처리하지 못했을 수도 있기 때문이다. 따라서 생산자B가 완료 할 때까지 기다려서 생산자 A는 확정 처리 해야한다. 

소비자의 경우는 일단 WaitFor 하다가, 이전에 자기가 처리 했던 슬롯위치 +1 의 위치의 값을 가져오는데, 생산자가 입력을 했는지도 확인을 해서 ㅇㅋ 면 값을 가져와서 처리한다. 또한 생산자의 속도가 빨라서 거리가 많이 벌어졌다면, 소비자는 범위를 크게 잡아서 데이터를 가져 갈 수도 있기 때문에, (batching effect) 밸런싱 맞추는것도 쉬워집니다. 주요 단점으로는 소비자가 여럿일 때 하나의 소비자에서 지체현상이 일어난다면, 해당 슬롯이 막혀서 생산자는 더이상 추가를 못하게 되고, 따라서 소비자들도 정체 될 것입니다.  생산자,소비자가 여러개 일 경우 등 좀 더 구체적인 내용에 대한 설명은 다음 세 글을 참고하세요 

-> Trish’s post codeaholics's post, 쭌안아빠님 블로그

또한 리팩토링의 저자이자 패턴계의 스승이신 마틴파울러는 2011년 그의 글에서 LMAX Architecture 이 왜 만들어졌는지에 대한 전반에 대해 말하고 있으며, 블록체인(bitshare)에서 사용된 LMAX Architecture 에 대한 글 링크를 소개합니다.

또한 리팩토링의 저자이자 패턴계의 스승이신 마틴파울러는 2011년 그의 글에서 LMAX Architecture 이 왜 만들어졌는지에 대한 전반에 대해 말하고 있으며, 블록체인(bitshare)에서 사용된 LMAX Architecture 에 대한 글 링크를 소개합니다.


Distruptor 패턴 과 블록체인

마지막으로 다시 블록체인으로 돌아와 보죠.

먼저 EOS,스팀에서  LMAX Architecture 를 통해 얻은 교훈은 아래와 같습니다.

ㅡ 모든것은 메모리에 유지시키자.

ㅡ 코어 비지니스 로직은 무조건 싱글 쓰레드에서 처리한다.

ㅡ 암호화에 관련된 조작들은 (해싱,서명) 코어 비지니스 로직 외부로 꺼낸다.

ㅡ 상태의존적인 검증과 상태독립적인 검증을 나눈다

ㅡ 데이터 모델로 부터 만들어진 객체를 사용한다.

그리고 직접 소스를 살펴보며 적용된 것을 개발자 시각으로 생각해 볼것은 아래와 같습니다.

ㅡ 왜 굳이?

ㅡ 어떻게 구현 해 있나?

ㅡ 개선방안은?

이 있겠으며, 

좀 더 넓은 시야에서는 

ㅡ 비트코인,이더리움에서는 신뢰의 속도 때문에 Distrupt 패턴이 무의미 한데, 스팀,EOS 는 삐른 대신 대신 신뢰의 속도를 포기 한건지?

ㅡ 어떻게 보면 얄팍하기 까지 보이는 EOS,STEEM 의 신뢰의 속도 처리 방안에 비해 이더리움은 앞으로 어떻게 묵직히 처리 할 것인지.

를 지켜보는 재미가 있다고 볼 수 있습니다.


* 모든 기술에 은탄환은 없으니, Distruptor 패턴도 자신의 상황에 맞춰서 잘 적용하는것이 중요 할 것입니다.
* 이 포스트의 최종목적이 EOS 에서 사용된 LMAX Architecture 해부 인데, 어느정도 분석이 되면 따로 글을 파겠습니다.


레퍼런스:

https://medium.com/corda/transactions-per-second-tps-de3fb55d60e3
http://blog.codeaholics.org/2011/the-disruptor-lock-free-publishing/
https://martinfowler.com/articles/lmax.html
http://goodjoon.tistory.com/254
http://www.smallake.kr/?p=2254
https://www.slideshare.net/trishagee/introduction-to-the-disruptor?next_slideshow=1
http://mechanitis.blogspot.kr/2011/07/dissecting-disruptor-writing-to-ring.html

 

이더리움에서 활용하고 있는 "머클 패트리샤 트리"를 이해하려다가 주화입마에 빠지신 분들이 꽤 있는거 같은데요. 사실 그것은 당신들의 탓이 아닙니다. 비탈릭이 쓴 머클링 in 이더리움 등의 블로그 글 및 각종 이더리움 책에서 설명된 내용을 읽고 금방 이해하는게 사실상 쉽지 않습니다. 남을 이해시키기 위한 글이 아니라는 생각이 들기도 하는데요.

이 글에서 저는 "이해시켜 드리기 위한 글"을 써보려고 합니다. 만약 또 실패한다면 역시 제가 모자라서 그런것이 겠지요. "개념을 이해시켜 드리기 위한 글" 이기 때문에 아주 디테일한 부분(트리에 대한 구체적 요소,추가,삭제,업데이트 및 블록상세등)을 과감히 생략하였습니다. 지엽적인 오류도 있을 수 있습니다. OTL

머클패트리샤트리를 이해하기 위해서는 먼저 몇가지 짚고 넘어가야 할 것이 있습니다. 먼저 용어의 재정의 인데요.
보다 선명한 이해를 위해서는 비트코인의 "머클 트리"는 "바이너리 머클 트리" 라고 말해야 하며, 이더리움의 "머클 패트리샤 트리" 는 "상태전이 일반 머클 확장 페트리샤 트리" 라고 말해야 합니다. 각각의 단어마다 중요한 의미를 가지고 있으며 본 글을 통해 차차 설명드릴 예정입니다.

이제 시작해 볼까요?

 "상태전이"  "바이너리 머클 트리" "상태전이 일반 머클 트리"  "확장 페트리샤 트리"  각각의 어떤 의미를 가지는지 봅시다. 마지막엔 모두 합쳐서~"상태전이 일반 머클 확장 페트리샤 트리" !!!

1. "상태전이"

먼저 여기 아이언맨이 있습니다. 

"어벤져스 인피니티 워" 의 한장면인데요. 현재 아이언맨의 체력수치는 "100" 입니다.  아이언맨 역할을 한 로버트 다우니 주니어는 체력 "100"으로 역할을 수행하고 있습니다.

하지만 우주가 파멸되는 것을 볼 수 없었던 "타노스" 의 주먹 한방에 체력 50이 고갈됩니다.
이때, 이전의 체력 "100" 이었던 로버트다우니주니어를 영화에서 제외해서 구경시키고, 체력"50"인 이윤석을 투입해서 아이언맨의 역할을 수행하게 합니다. 좀 말이 안되는거 같나요? 즉 어떤 변화가 일어 났을때 "교체" 합니다. 이것은 상태 전이(변화)가 아닙니다. 

프로그래밍적으로 보면 immutable 속성이라고 하며,  

a = 100; 

이런 a 를 100에서 50으로 값을 변경 할 경우 그냥 기존 a 에 50 을 넣어서 변경하는것이 아니라, 기존 100은 놔준채 50을 가진 새로운 a를 만들게 되는 방식입니다. 딱 봐도 먼가 불필요한 공간을 낭비하는것 처럼 보이죠? 하지만 장점도 크기 때문에 현재 프로그래밍 언어에서 주목받는 방식이기도 합니다.

이것이 "비트코인"에서 처리하는 방식입니다. "교체" , "기존 것은 놔두고 새로운것을 만든다" 

근데 이더리움에서는 그냥 상식적인(?)인 방식을 사용합니다. 그냥 체력이 줄어든 기존 아이언맨이 계속 타노스와 싸우는 거죠. 즉 상태가 변경된다는 것이죠. 이것을 고상하게 "상태 전이"라고 합니다. 아무것도 아니죠 사실.

블록체인(비트코인)틱한 예로 들어 볼까요? (디테일은 이렇지 않습니다. 예를 위한 것임) 

철수가 이전에는 100원 이었고 현재는 60원입니다. (영희는 120원, 위에 그림 잘못;) 이전블록에도 있고 현재 블록에도 기록되어 있네요. 다른 사람들 기록도 마찬가지입니다. 먼가 공간을 낭비하는 듯한 느낌입니다.

좀더 구체적으로 보면 

위의 그림에서는 철수의 80이 50으로 교체되고, 영희는 10을 40으로 상태가 변이된 것이 아니라, 30이 새로 생성되었습니다. 즉 비트코인은 "추가,삭제" 방식으로 이루어지고, 이더리움은 아주 상식적인 "변이" 방식으로 이루어집니다. (이더리움의 모든것이 "상태변이"로 이루어지는 것은 아닙니다)

이제 이더리움식 상태전이를 보겠습니다.

33블록과 34블록은 하나를 가르키고 있으며, 철수와 영희에 대한 상태가 변경(전이)되었을 뿐입니다. 공간의 낭비가 줄어 들었습니다.
(* 위는 이해를 위한 예이고 정확히는 이더리움의 state 저장은 항상 최신본만 유지하는게 아니라, history 또한 유지합니다.  과거 데이터를 유지할 이유가 없으면 그냥 update 해도 되지만, 이더리움은 불안요소를 가지고 있습니다. 따라서 실제는 변하지 않은 데이터는 계속 유지하되, 변한 데이터는 update 하지 않고 append 합니다. 남은 데이터는 나중에 삭제합니다. 가지치기(prunning) 이라고 하는데 비탈릭이 한번 개념을 제시했었는데 문제가 있다고 하며, 현재 정확히 어떻게 구현되어 있는지는 확인 안해봤습니다) 

2. "바이너리 머클 트리"

이제 비트코인에서 사용되는 바이너리 머클 트리에 대해서 알아보죠. (디테일은 좀 다릅니다)
일단 바이너리 머클 트리는 누군가는 조금 공간을 더 쓰지만 전체적으로 보면  "공간을 절약" 하기 위해 만들어진 놈입니다. 특히 무엇이 포함되어 있는가? 에 대한 질문에 전체를 가지고 있지 않으면서도 대답을 할 수 있게 해줍니다.

바이너리 머클트리는 구체적인 정보(여기서는 철수100, 영희50등등) 를 가지고, 두개씩 쌍을 이뤄서 축약시키고, 그것을 다시 축약시켜서 하나로 만드는 것을 말합니다. 그림에서 보다시피 2개씩 쌍으로 되어서 바이너리이구요. 반드시 2개씩 쌍을 만들지 않아도 됩니다. (비트코인에서만 2개씩 쌍인 것이구요)

즉 구체적인 정보 + 축약정보의 트리인데요. 결국 공간을 더 낭비하게 됩니다. 잉? 공간을 절약한다매? 
네 비트코인에는 노드들이 다양하게 있는데, 이 구체적인 정보 + 축약정보트리를 모두 가지고 있는 노드에게는 낭비가 맞습니다만, 라이트 노드는 단지 블록헤더만 가질수 있게 되어 전체적으로 보면  획기적인 절약이 됩니다. 

보통 거래를 검증하기 위해서는 위의 그림을 예로들면 정은이가 30을 가지고 있음을 확인해야 하는데요. 블록헤더만 가지고서 그것을 가능하게 합니다. 어떻게 할까요? 새 그림을 보시죠.

실제는 이렇습니다. 위의 그림에서 거래0 이라고만 적혀있지만 데이터가 꽤 많아요. 그 큰 거래데이터를 해쉬하여 해쉬0,1,2,3을 만들고 그것을 또 해쉬 하여 루트해시를 만드는데요.

루트해시만을 가지고 있는 라이트노드는 거래3이 이 블록에 있는지 확인을 받기 위해서 모든것을 가지고 있는 풀노드에게 요청하면 해쉬2와 해시01에 대한 정보만 전달해주는데, 이것을 가지고 순서대로 재계산하여 루트해시를 만들수 있게  되며, 그 루트해시 값이 자신이 가지고 있는 루트해시값과 같으면 "아~ 거래3는 이 블록에 있는게 맞구나" 하고 검증성공하게 합니다.

이게 비트코인에서 사용되는 바이너리 머클 트리인데요. 이제 중요한 포인트가 나옵니다. 위의 "상태전이" 에서 설명했듯이 비트코인 같은 경우는 이 바이너리 머클 트리를 구성하는 내용들이 모두 새로 만들어 집니다. 즉 업데이트(전이) 가 아니라 새롭게 창조됩니다. 따라서 아래와 같은 모습을 띄게 됩니다.

블록마다 새로운 내용을 담고 있습니다. (위의 상태전이에서 설명한 것처럼 상태가 전이되는 방식이 아니라, 새로 생성되는 방식. 아이언맨 100이 아이언맨60이 되는게 아니라, 아이언맨 60이 새로 생성되는 방식)

그럼 상태를 전이 시키려면 어떻게 해야 할까요? 매우 단순합니다. 어찌보면 이게 더 자연스럽구요.

3. "상태전이 일반(non-binary)머클 트리"

(아직까지 "패트리샤" 에 대한 내용은 하나도 안나왔으며, 위의 그림도 그것과는 무관합니다.)

위의 그림을 보면 2개의 블록이 있으며, 두 블록이 동일한 머클 트리를 공유하고 있습니다. 여기서 보시면 트리가 2개씩 뻗어나간게 아니죠? 그래서 일반(non-binary)머클트리라고 칭했으며, 상태전이가 일어나고 있습니다. 즉 블록마다 새로운것만 가지고 있는게 아니라, 공통된것은 공유하고 있으며, 변경(전이) 된 것만 마지막 트리에서는 변경(정확히는 추가)하여 사용하고 있습니다.

이렇기 때문에 공간을 엄청나게 절약할 수 있음을 알수 있습니다. 

(* 다시 말하지만 정확히는 이더리움의 state 저장은 항상 최신본만 유지하는게 아니라, history 또한 유지합니다. 위 그림에서 27이 없어져야 하는데, 사실 일정동안 남겨 두긴 합니다. 이유는 합의가 이루어져서 블록이 만들어 졌다고 해도 확정이 되지 않는 불안 요소가 비트코인이나 이더리움에 있기 때문입니다. bifurcated 이 생기면 state rollback 을 해야 하기 때문. 따라서 시간이 어느정도 흐르면 pruning 과정을 통해 이 history 를 삭제하여 저장 공간을 줄여 줍니다. 실제는 update가 아니라 append 라는 점이죠.) 

4. "패트리샤 트리"

이제 패트리샤 트리에 대한 설명을 시작합니다. 이것도 역시 공간을 절약하기 위함입니다.

인터넷에서 젤 흔히 볼수 있는 그림이며, 이 자체로 아주 명쾌하게 설명해 주고 있습니다.  1번에서 7번까지의 단어를 모두 기록하는 것보다 각 공통되는 부분은 공유하는게 공간을 절약할 수 있게 되겠지요.  r,om, ub, an 이나 ic 처럼 공통되는 부분을 하나의 노드로 공유합니다. 이더리움에서 사용되는 Account 가 State tree, Transactions Tree등에 주렁주렁 달릴텐데...저럴게 매달아 놓으면 중복이 많이 없어지는거죠. 

5. "확장 패트리샤 트리"

이더리움 같은 경우 패트리샤 트리를 좀 다르게 사용하는데요. 이것 역시 그림 그 자체로 이해 할 수 있을 것 입니다.

3개의 특징을 가진 노드가 있습니다.

첫째. 리프노드 

 - 이 노드는 키값에 대한 패스가 종결되었을 때 value 에 해당 주소에 대한 정보들을 포함하고 있게 됩니다.
   위의 그림에서는 a77d337 는 value 로 1.00WEI 를 가지고있네요.(실제 이더리움 구조에서는 더 복잡한 value 를 갖게 됩니다)

둘째. 브랜치노드

- 이 노드는 16개의 16진수 값과 value 를 가지고 있게 됩니다. 이 노드를 통해서 가지치기가 시작됩니다.

셋째, 확장노드

- 이 노드를 통해서 공유되어질 키값들이 저장되게 됩니다. 이 노드 자체로는 종결되지 않기 때문에 value 로는 이후의 키 값을 책임질 브랜치 노드를 갖게 됩니다.

참고로 이 그림에서는 "머클" 과 "상태전이" 의 모습은 없습니다. 

6. "상태전이 일반 머클 확장 페트리샤 트리

"상태전이" + "머클트리" + "확장패트리샤 트리""상태전이 일반 머클 확장 패트리샤 트리" 

그림을 다시 봅시다. (다시 언급하지만 정확한 디테일을 담고 있지 않습니다.) 

보다시피 확장 패트리샤 구조를 가지고 있으며, 그 구조에서 각 패트리샤 노드들의 해쉬가 합쳐져서 위쪽을 향하고 있습니다. 즉 머클링을 하고 있는거죠. 비트코인처럼 2개씩 쌍을 지을 필요는 없습니다. 결국 이 머클링이 이어지면 루트해시가 만들어지게 되며, 자식에 어떤 변화가 생기면 루트해시의 값도 바뀝니다. 이 그림에서 "상태전이"가 표현되지 않았지만, 2e: 200원이 이전 블록에서는 2e: 300원이 었으며 300->200으로 "상태전이"가 일어난 후의 모습이라고 생각하시면 될 거 같습니다. 이 상태전이 때문에 새로운 블록의 상태트리의 루트해시 값도 바뀌게 될 겁니다.

* 참고로 이더리움은 1개의 "상태전이 머클 패트리샤 트리(어카운트별 상태(코인 및 코드) 저장용)" 와  2개의 상태전이는 빠진 "머클 패트리샤 트리(트랜잭션,영수증용)" 로 이루어져 있다.


이쯤에서 글을 마칠까 합니다. 이 글을 읽고 전반적으로 "아하~~" 라는 느낌을 가질 수 있다면


1) https://steemit.com/cryptocurrency/@nadifsd/merkle-patricia-tree 
2) https://medium.com/coinmonks/data-structure-in-ethereum-episode-3-patricia-trie-b7b0ccddd32f

을 통해서 디테일한 부분은 금방 해소 될 수 있으리라 보여집니다. ^^ 

또한  이더리움은 PBFT 와는 다르게 "Safety" (분산 시스템에서 말하는 Safety vs Liveness)가 약하며 그러기 때문에 history 를 관리해야 하고 pruning 이라는 주제도 나옵니다. 본문에는 모든게 생략되어 있습니다. 다음 여행지를 찾아 떠나세요~~ 

 

 

- 발표시간 : 2018년 4월14일 (토요일) 3시~5시  

- 발표주제 :

은행/사이버보안/선거관리/웹호스팅/도매,소매공급망/엔터네이먼트등 블록체인은 예상치 못한 방식으로 수많은 산업에 영향을 미칠 엄청난 미래 기술이 될 것이다라는 다소 과장됬을지도 모르는 글들이 쏟아져 나오는 현재 있어서, 우리 개발자들은 어떻게 보면 복받았을지도 모른다고 생각합니다. 그런 엄청난 기술에 대해서 일반인들 처럼 가쉽성 기사들만 읽고 지나가는게 아니라, 그 실체 기술이 무엇인지에 대한 호기심을 비교적 쉽게 풀 수 있기 때문인데요. 세상이 바뀌는 것에 함께 한다는 것은 꽤 멋진 일인거 같습니다. 
그리고 블록체인을 공부하는 것은 우리 소프트웨어 개발자에겐 일석이조의 효과를 가져오는데요. 블록체인 자체를 알게 되는 것과 동시에, 개발자로써의 내공을 쌓는 하나의 교육 과정이 될 수 도 있습니다. 오픈소스 리딩과 함께 여러 알고리즘을 공부하게 되니까요.여러분의 실무에 써먹을수도 있는 그런 기술일지도 모릅니다. 

블록체인에 대해서 짧게 소개하자면 블록체인은 데이터를 블록이라는 개념으로 연결지어 분산 노드를 통해서 관리되고 서로 통신하며, 해당 노드는 누구나 될 수 있습니다. 이러한 누구나 가지고 있을 수 있는 데이터를 어떻게 신뢰 할 수 있을까? 그것에 대한 해답으로 나온 기술로써, 이  블록체인(비트코인,이더리움)이라는 거대한 아이디어의 기반이 되는 다양한  요소 기술들에 대해서 간단히 살펴보며 마지막으로는 전체에 대한 조망을 하면서 마무리 짓겠습니다.



1.블럭체인(비트코인) 인사이드 

발표 순서

- 책 소개 (마스터링 비트코인, 마스터링 이더리움) 
- 신뢰란 무엇인가 
- 분산컴퓨팅
- 비잔틴 장군 문제
- 블록체인과 비트코인
- 블록체인 세대별 기술
- 대칭키/공개키 암호화/서명이란
- 해시 
- 가쉽 네트워킹 및 비트코인 네트워크 

- 분산 데이터베이스

- 주소 만들기 
- 머클트리 
- 블룸필터 
- 스크립트 
- 거래검증 
- 거래생성
- 채굴 및 작업증명 
- 블록생성
- 블록검증
- 블록선택 및 전파
- 전체 조망  <-- 이번 세미나는 여기까지 



2.이더리움 코어 및 기타 스토리

- 작업증명(PoW, Proof-of-work)과 지분증명(PoS, Proof-of-stake) 
- state,balance,accounts  vs UTXO
- RLP ,HP 엔코딩  & 굳이 왜? 
- 이더리움 머클 패트리샤 트리 및 상태전이 증명 (거래 검증)
- 상태트리, 거래트리, 영수증 트리 (굳이 왜 영수증 트리?)
- 이더리움 스마트 컨트랙트
- 이더리움 ERC20 & ERC721
- 이더리움 스크립트 
이더리움 네트워킹
   ㅁ TCP/UDP/NAT/홀펀칭/UPnP   : 이더리움 nas 옵션 인사이드
   ㅁ Kademlia (Consistent Hashing & DHT)
   ㅁ RLPx/devP2P
   ㅁ ethereum wire protocol 과 블록전파 
- 이더리움 스웜 
   ㅁ bitTorrent
   ㅁ Filecoin ( IPFS)
- 이더리움 플라즈마
- 이더리움 샤딩
- 이더리움 위스퍼
- 이더리움 캐스퍼 POW 와 POA 의 조합 
Multi-sig ,오라클,prunning,등등
- 라이트닝 네트워크,Raiden 네트워크
- 패리티를 통한 콘소시엄 블록체인 구성하기  
- 사이드체인&two way peg
- 사이드체인을 통한 이더리움 DApp 확장 (loom network DAppChains)
- DEVP2P (GO,PYTHON) 코드리딩

3. ColoredCoin,zcash,모네로,하이퍼레저,CORDA스트라티스,리플,스텔라,카르다노,스팀,EOS,,IOTA,ICON,코스모스

-  각 플랫폼의 특성 (비트코인 확장,BaaS,프라이빗,금융,커뮤니티,범용,사물인터넷,인터커넥션등) 
-  비트코인(家) 의 확장:  알트코인/사이드체인 (단순화 화폐에 자산등록,자산발생,자산중심 플랫폼으로)
-  익명성 : 모네로의 링시그니쳐 와 zcach 의 영지식증명 
-  리플에서 집중하는 이체 시장 ( PG,은행간 국제송금, 기업간 국제송금, 은행 지점간 내부송금)
-  다른 플랫폼들을 Private 하게 사용하는것과 스트라티스,하이퍼레저,CORDA 의 차이점
-  Private 블록체인이 필요한가? 
-  하이퍼레저,CORDA 은 블록체인인가?  분산원장 VS 블록체인
-  금융업계와 블록체인 : 리플 vs CORDA
-  Hyperledger Fabric vs CORDA
-  POW,POS 말고 POA : 확률적 투표 이론, PBFT : 그리고 이들의 조합 캐스퍼  FFG
-  PBFT -> SIEVE
-  리플은 실제 사용되고 있는가? 리플과 스텔라의 차이점
-  스팀은 데이터를 어디에 저장하나? 비용은?  그리고 Graphene 네트워킹 
-  스팀,EOS 는 뭘 믿고 거래비용을 사용자에게서 해방시켰나?  수수료 vs  비율제한
-  ICON 은 분산합의 시스템 인가? 아닌가?
-  ICON 과 코스모스가 과연 inter-connection 역할을 할 수 있을까? 한계는?
-  IOTA 블록체인이 없다고?  마코프 체인 몬테카를로 알고리즘이란
-   ....


4. 다양한 블록체인에 사용되는 기술들 
- 네트워크 샤딩(Network Sharding), 트랜잭션 샤딩(Transaction Sharding), 연산 샤딩(Computational Sharding)
- 다중서명/집계된 서명(aggregated signature)
- 암호화 (ECDSA , ECIES , ECDH)
- 토큰 이코노미 & 암호 이코노미 
 PBTF 를  2단계 실행으로 변경한다면? (즉 브로드캐스팅을 한번만 한다면?) 
- ...


5. 블록체인에 관련된 비기술 이슈들 
-  거래소 이슈
-  ICO 이슈 
- 국가별,기관별 규제 이슈 
- 비트코인(家) 이야기 
- ...

6.이더리움 Dapp 응용 개발

- 분산 어플리케이션 이란?
- 장,단점
- ERC
- 로직과 저장소
- 오프체인&오라클  
- 솔리디티 프로그래밍
- Web3.js & Electron 
- 존재증명 및 크라우드펀딩 개발 

7. 하이퍼레저 패브릭 이란?

- 개념(network,identity,membership,peers,private data,ledger 등)
- 네트워크 만들기
- 체인코드 만들기
- 미들웨어 만들기
- 웹어플리케이션 만들기
- 하이퍼레저 컴포저
- IBM Bluemix & AWS Blockchain template 
- 하이퍼레저 sawtooth 는 무엇이며 fabric 과의 차이점은?


8. Corda 란?  


- 개념(네트워크,ledger,state,notary,contracts,Tranaction,Flow 등)
- 네트워크 만들기
- CorDapp 만들기

9 .EOS  & STEEM 응용 개발
 
10. DAppChains (loom) 응용 개발 

11. COSMOS 응용 개발 

12. 블록체인 기본/응용 서비스들

- 현재 존재하는..
- 앞으로 존재 할..
- 기록물/저작권 보관 (등록) 
- 커뮤니티 보상 서비스 
- 엔터테이먼트 서비스 (음악,영상,카툰등) 
- 게임,가상현실에서의 자산으로써의 가치
- 유통,운송,무역
- 정치,공공재 
- ...

발표자료 

개발자를 위한 비트코인(블록체인)_ver발표.pdf





scriptSig 스크립트의 시작에 나오는 sig 가 어떻게 만들어지는지 궁금하진 않는가? 즉 무엇을 서명한 것인가? 
그에 대한 답이 아래 코드에 있다. 이렇게 복잡하게 만들어 질 줄이야 ㅎㅎ 

관련 레퍼런스: http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html
코드출처: https://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx

트랜잭션이 만들어지는 단계 (19단계) :

  1. 4바이트의 버전 필드 추가 : 01000000
  2. 1바이트의 몇번째 input 인지 추가: 01
  3. 우리가 소비하고자 하는 지난번 트랜잭션의 32-byte 해시: 
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
  4. 4바이트의 3번의 트랜잭션에서의 output 인덱스의 UTXO (output number 2 = output index 1):
     01000000
  5. 트랜잭션을 서명할 목적으로 임시 scriptSig 를 만드는데, 1바이트의 길이 (0x19=25bytes) 를 채운다 :
     19
  6.  우리가 사용 할 UXTO의  scriptPubKey 를 이용해서 임시 scriptSig 를 만든다:
     76a914010966776006953d5567439e5e39f86a0d273bee88ac
  7. 4바이트 시퀀스 필드를 채운다: ffffffff
  8. 1바이트의 새로운 트랜잭션의 아웃풋 넘버를 채운다 : 01
  9. 거래 할 금액에 대해 8바이트 필드를 쓴다. 우리가 소비할 수 있는 총 UTXO 에서 fee 를 위한 0.001BTC 를 제외한다. (0.999 BTC, or 99900000 Satoshis): 605af40500000000
  10. output 스크립트의 길이를 1바이트로 채운다.(0x19 or 25 bytes): 19
  11. 실제 아웃풋 스크립트를 작성한다(
    대략  OP_DUP OP_HASH160 pubKeyHash  OP_EQUALVERIFY OP_CHECKSIG 이런것이 들어간다.pubKeyHash 는 public key 를 해쉬한 값인데, 내가 돈을 보내는 사람의 주소에서 생성 될 수 있다.):
     76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
  12. 4바이트 "lock time" 필드를 채운다: 00000000
  13. 4바이트의 "hash code type" 를 쓴다.(우리 경우는 1이며 SIGHASH_ALL): 01000000

    (일단 여기까지 완전한 트랜잭션이 만들어 졌다. 이후에는 이 트랜잭션에 대한 서명을 할 차례이다.) 

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    19
    76a914010966776006953d5567439e5e39f86a0d273bee88ac
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000
    01000000
    
  14. 이중-SHA256 해시로 이 전체 구조에 대해서 해쉬한다.:
     9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e

  15. 개인키로 private/public 키 쌍을 생성한다. 그리고 14번의 해시를 서명한다:
     30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06 이 서명에 뒤에 1바이트 해시코드를 넣는다. 01. 공개키는 다음과 같다: 0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  16. 진짜 scriptSig 를 구축한다: <One-byte script OPCODE containing the length of the DER-encoded signature plus 1 (the length of the one-byte hash code type)>|< The actual DER-encoded signature plus the one-byte hash code type>|< One-byte script OPCODE containing the length of the public key>|<The actual public key>
  17. 스텝 5에서의 scriptSig 길이를 진짜 scriptSig 의 길이로 변경한다 : 8c
  18. 스텝 6에서 생성한 임시 scriptSig 를 스텝16의 진짜 scriptSig 로 변경한다:
     4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  19. 마지막으로 스텝 13에서 추가한 4바이트 해시 코드타입을 제거한다. 

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    8c
    4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000

https://hamait.tistory.com/1003  <-- 여기로 가면 위의 과정을 좀 더 비쥬얼하게 만들어 놓았다.

위 19단계에 해당하는 파이썬 코드는 다음과 같다.

#bitcointools
from deserialize import parse_Transaction, opcodes
from BCDataStream import BCDataStream
from base58 import bc_address_to_hash_160, b58decode, public_key_to_bc_address, hash_160_to_bc_address

import ecdsa_ssl

import Crypto.Hash.SHA256 as sha256
import Crypto.Random

#transaction, from which we want to redeem an output
HEX_TRANSACTION="010000000126c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb000000008b48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078ffffffff02b06c191e010000001976a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac00e1f505000000001976a914010966776006953d5567439e5e39f86a0d273bee88ac00000000"

#output to redeem. must exist in HEX_TRANSACTION
OUTPUT_INDEX=1

#address we want to send the redeemed coins to.
#REPLACE WITH YOUR OWN ADDRESS, unless you're feeling generous
SEND_TO_ADDRESS="1L4xtXCdJNiYnyqE6UsB8KSJvqEuXjz6aK"

#fee we want to pay (in BTC)
TX_FEE=0.001

#constant that defines the number of Satoshis per BTC
COIN=100000000

#constant used to determine which part of the transaction is hashed.
SIGHASH_ALL=1

#private key whose public key hashes to the hash contained in scriptPubKey of output number *OUTPUT_INDEX* in the transaction described in HEX_TRANSACTION
PRIVATE_KEY=0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

def dsha256(data):
return sha256.new(sha256.new(data).digest()).digest()

tx_data=HEX_TRANSACTION.decode('hex_codec')
tx_hash=dsha256(tx_data)

#here we use bitcointools to parse a transaction. this gives easy access to the various fields of the transaction from which we want to redeem an output
stream = BCDataStream()
stream.write(tx_data)
tx_info = parse_Transaction(stream)

if len(tx_info['txOut']) < (OUTPUT_INDEX+1):
raise RuntimeError, "there are only %d output(s) in the transaction you're trying to redeem from. you want to redeem output index %d" % (len(tx_info['txOut']), OUTPUT_INDEX)

#this dictionary is used to store the values of the various transaction fields
# this is useful because we need to construct one transaction to hash and sign
# and another that will be the final transaction
tx_fields = {}

##here we start creating the transaction that we hash and sign
sign_tx = BCDataStream()

##first we write the version number, which is 1
tx_fields['version'] = 1
sign_tx.write_int32(tx_fields['version'])

##then we write the number of transaction inputs, which is one
tx_fields['num_txin'] = 1
sign_tx.write_compact_size(tx_fields['num_txin'])

##then we write the actual transaction data
#'prevout_hash'
tx_fields['prevout_hash'] = tx_hash
sign_tx.write(tx_fields['prevout_hash']) #hash of the the transaction from which we want to redeem an output
#'prevout_n'
tx_fields['output_index'] = OUTPUT_INDEX
sign_tx.write_uint32(tx_fields['output_index']) #which output of the transaction with tx id 'prevout_hash' do we want to redeem?

##next comes the part of the transaction input. here we place the script of the *output* that we want to redeem
tx_fields['scriptSigHash'] = tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey']
#first write the size
sign_tx.write_compact_size(len(tx_fields['scriptSigHash']))
#then the data
sign_tx.write(tx_fields['scriptSigHash'])

#'sequence'
tx_fields['sequence'] = 0xffffffff
sign_tx.write_uint32(tx_fields['sequence'])

##then we write the number of transaction outputs. we'll just use a single output in this example
tx_fields['num_txout'] = 1
sign_tx.write_compact_size(tx_fields['num_txout'])

##then we write the actual transaction output data
#we'll redeem everything from the original output minus TX_FEE
tx_fields['value'] = tx_info['txOut'][OUTPUT_INDEX]['value']-(TX_FEE*COIN)
sign_tx.write_int64(tx_fields['value'])

##this is where our scriptPubKey goes (a script that pays out to an address)
#we want the following script:
#"OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG"
address_hash = bc_address_to_hash_160(SEND_TO_ADDRESS)

#chr(20) is the length of the address_hash (20 bytes or 160 bits)
scriptPubKey = chr(opcodes.OP_DUP) + chr(opcodes.OP_HASH160) + \
chr(20) + address_hash + chr(opcodes.OP_EQUALVERIFY) + chr(opcodes.OP_CHECKSIG)

#first write the length of this lump of data
tx_fields['scriptPubKey'] = scriptPubKey
sign_tx.write_compact_size(len(tx_fields['scriptPubKey']))

#then the data
sign_tx.write(tx_fields['scriptPubKey'])

#write locktime (0)
tx_fields['locktime'] = 0
sign_tx.write_uint32(tx_fields['locktime'])

#and hash code type (1)
tx_fields['hash_type'] = SIGHASH_ALL
sign_tx.write_int32(tx_fields['hash_type'])

#then we obtain the hash of the signature-less transaction (the hash that we sign using our private key)
hash_scriptless = dsha256(sign_tx.input)

##now we begin with the ECDSA stuff.
## we create a private key from the provided private key data, and sign hash_scriptless with it
## we also check that the private key's corresponding public key can actually redeem the specified output

k = ecdsa_ssl.KEY()
k.generate(('%064x' % PRIVATE_KEY).decode('hex'))

#here we retrieve the public key data generated from the supplied private key
pubkey_data = k.get_pubkey()

#then we create a signature over the hash of the signature-less transaction
sig_data=k.sign(hash_scriptless)

#a one byte "hash type" is appended to the end of the signature (https://en.bitcoin.it/wiki/OP_CHECKSIG)
sig_data = sig_data + chr(SIGHASH_ALL)

#let's check that the provided privat key can actually redeem the output in question
if (bc_address_to_hash_160(public_key_to_bc_address(pubkey_data)) != tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2]):
bytes = b58decode(SEND_TO_ADDRESS, 25)
raise RuntimeError, "The supplied private key cannot be used to redeem output index %d\nYou need to supply the private key for address %s" % \
(OUTPUT_INDEX, hash_160_to_bc_address(tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2], bytes[0]))

##now we begin creating the final transaction. this is a duplicate of the signature-less transaction,
## with the scriptSig filled out with a script that pushes the signature plus one-byte hash code type, and public key from above, to the stack

final_tx = BCDataStream()
final_tx.write_int32(tx_fields['version'])
final_tx.write_compact_size(tx_fields['num_txin'])
final_tx.write(tx_fields['prevout_hash'])
final_tx.write_uint32(tx_fields['output_index'])

##now we need to write the actual scriptSig.
## this consists of the DER-encoded values r and s from the signature, a one-byte hash code type, and the public key in uncompressed format
## we also need to prepend the length of these two data pieces (encoded as a single byte
## containing the length), before each data piece. this length is a script opcode that tells the
## Bitcoin script interpreter to push the x following bytes onto the stack

scriptSig = chr(len(sig_data)) + sig_data + chr(len(pubkey_data)) + pubkey_data

#first write the length of this data
final_tx.write_compact_size(len(scriptSig))

#then the data
final_tx.write(scriptSig)

##and then we simply write the same data after the scriptSig that is in the signature-less transaction,
# leaving out the four-byte hash code type (as this is encoded in the single byte following the signature data)

final_tx.write_uint32(tx_fields['sequence'])
final_tx.write_compact_size(tx_fields['num_txout'])
final_tx.write_int64(tx_fields['value'])
final_tx.write_compact_size(len(tx_fields['scriptPubKey']))
final_tx.write(tx_fields['scriptPubKey'])
final_tx.write_uint32(tx_fields['locktime'])

#prints out the final transaction in hex format (can be used as an argument to bitcoind's sendrawtransaction)
print final_tx.input.encode('hex')





* 이 글은 누군가를 이해 시키기 위한 글이 아닙니다. 블록체인을 처음 만난 개발자가 비트코인의 핵심이라 할 수 있는 거래(검증)에 대한 공부/삽질을 하며 겪는 여정으로써 굉장히 불친절한 글이라는 것을 말씀드립니다. 그냥 제 삽질일기~




그림 7-5에서는 단지 32바이트 크기의 해시 4개의 길이(총 128바이트)인 머클 경로를 생성함으로써 거래 K 가 블록 내에 포함되어 있다는 사실을 노드가 입증할 수 있다는 것을 보여준다. 머클패스는 HL .......(그림에서 파랑) 등등 이렇게 4개의 해시로 구성되어 있다. 인증 경로로 제공된 이 4개의 해시를 가지고 어떤 노드라도 4개의 해쉬에 대응하는 해시쌍인 H .... (그림에서 점선) 와 머클트리 루트를 계산함으로써 HK가 머클 루트에 포함되어 있다는 사실을 증명 할 수 있다.


마스터링 비트코인의 243p 나온 글이다. 해당 챕터를 읽어보면 머클트리가 왜 필요한지, 머클패스를 이용해서 검증하는 기술에 대해서는 이해하기 어렵지 않지만, 그럼 머클 패스를 던져주는 주체는 누구 인가에 대해서는 아리송 할 것이다. 즉 단위 기술의 이해 보다는 전반적인 스토리 이해의 부재를 느낄 것이다. (사실 저 책의 내용중 오역도 많은듯 하고, 실 저자가 친철하게 쓰지 못한 내용도 있다는 핑계를 대고 싶다. 뭐 책 두께가 한 800페이지는 되야 가능하겠지만.. 블록체인의 성경과 같은 보석같은 책에 대해 불손한 자세를 보인 것 같아서 자진 검열함) 

즉 저 책으로 처음 블록체인(비트코인)을 마주친다면 "HK가 검증 대상이면,그 쌍이 HL임을 어떻게  아는지? 이미 머클트리 상에 HK, HL 이 같이 있는 것을 확신 한다면 보낼 필요도 없는게 아닌지..?? 모든 노드가 참여하는지? 누가 보내는지?? 언제 보내는지? HK 거래는 현재 거래인가, 아니면 현재거래를 검증하기 위한 모든 과거 트랜잭션 중 하나 인가" 라는 합당한 의문들이 생기기 마련이다.

이에 대해서는 아래와 같이 추측을 했다. (사실이 아니다. or 아닐 수 도 있다.) 

"주변의 p2p의 상대 노드 중 풀노드는 거래 데이터를 받고 자신의 전체 블록체인과 대조하여 해당 거래의 유효성 확인하지만  p2p 상대가 SPV 이면 거래 데이터와 함께 "머클패스"를 보내서 검증에 참여 시킨다.

하나더

책에 있는 거래K도  현재 발생된 거래 그 자체라고 한참 오해했는데, 아니라고 추측 했다.

"현재 거래에 사용된 UTXO or 입력값 의 근거 과거 거래들 중 하나" 일 것이다.  

지금 거래의 입력값(or UTXO) 으로 사용된 코인이 합당한 것인지 어떻게 검증 할 수 있는지가 궁금해졌다.일단 현재 거래의 입력값을 검증을 위해서, 해당 입력값이 어떤 입력값에서 부터 나왔는지에 대한 내용을 블록체인에서 찾아야 한다고 짐작 할 수 있는데 아래의 두가지 궁금즘을 해결하기 위해서 먼저 비트코인을 구성하는 것들 중  4가지 구조를 한눈에 살펴보는것으로 시작하자.

@ 입력값과 UTXO 와의 구조적 차이점 (UTXO를 가지고 입력값 만들기? 출력값에서 UTXO 생성하기?) 
@ 기술적으로 입력값 or UTXO가 완성된 블록안의 머클트리에 존재하는 거래 해쉬와 어떻게 연관되는지


1. UTXO 구조

https://github.com/zoinofficial/zoin/blob/master/src/coins.h 

 CTransaction 의 잘라내진 (Pruned) 버전: 오직 메타데이터 및 UTXO 용으로 유지 

 시리얼라이즈 포맷:

  - VARINT(nVersion)

  - VARINT(nCode)

  - unspentness bitvector, for vout[2] and further; least significant byte first

  - the non-spent CTxOuts (via CTxOutCompressor)

  - VARINT(nHeight)

nCode 는 다음으로 이루어져있다.

  - bit 1: IsCoinBase()

  - bit 2: vout[0] is not spent

  - bit 4: vout[1] is not spent

  - The higher bits encode N, the number of non-zero bytes in the following bitvector.

  - In case both bit 2 and bit 4 are unset, they encode N-1, as there must be at

        least one non-spent output).

Example: 0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e

                 <><><--------------------------------------------><---->

                   |       \                            |                             /

              version   code             vout[1]                  height

 

     - version = 1

     - code = 4 (vout[1] is not spent, and 0 non-zero bytes of bitvector follow)

     - unspentness bitvector: as 0 non-zero bytes follow, it has length 0

     - vout[1]: 835800816115944e077fe7c803cfa57f29b36bf87c1d35

                * 8358: compact amount representation for 60000000000 (600 BTC)

                * 00: special txout type pay-to-pubkey-hash

                * 816115944e077fe7c803cfa57f29b36bf87c1d35: address uint160

     - height = 203998

 Example:                    

                01 09 0440 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b

           <><><--><---------------------------------------------------------><---------------------------------------------------><---->

             /    \      \                                |                                                                                    |                       /

    version  code  unspentness       vout[4]                                                                       vout[16]           height

 

   - version = 1

   - code = 9 (coinbase, neither vout[0] or vout[1] are unspent,

                 2 (1, +1 because both bit 2 and bit 4 are unset) non-zero bitvector bytes follow)

   - unspentness bitvector: bits 2 (0x04) and 14 (0x4000) are set, so vout[2+2] and vout[14+2] are unspent

   - vout[4]: 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4ee

              * 86ef97d579: compact amount representation for 234925952 (2.35 BTC)

              * 00: special txout type pay-to-pubkey-hash

              * 61b01caab50f1b8e9c50a5057eb43c2d9563a4ee: address uint160

   - vout[16]: bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4

               * bbd123: compact amount representation for 110397 (0.001 BTC)

               * 00: special txout type pay-to-pubkey-hash

               * 8c988f1a4a4de2161e0f50aac7f17e7f9555caa4: address uint160

   - height = 120891

 

- UTXO 는 블록과 별개로 LevelDB에 저장된다. 사용자의 지갑에 남은 돈이 얼마인지 빠르게 알 수 있게 해 준다.
입력값에 사용된 UTXO 는 데이타베이스에서 삭제될 것이고, 출력값은 새로운 UTXO를 데이터베이스에 입력 할 것이다.
- 아직 잘 모르겠다.


gettxout "txid" n ( include_mempool )

Returns details about an unspent transaction output.

Arguments:
1. "txid"             (string, required) The transaction id
2. "n"                (numeric, required) vout number
3. "include_mempool"  (boolean, optional) Whether to include the mempool. Default: true.     Note that an unspent output that is spent in the mempool won't appear.

Result:
{
  "bestblock" : "hash",    (string) the block hash
  "confirmations" : n,       (numeric) The number of confirmations
  "value" : x.xxx,           (numeric) The transaction value in BTC
  "scriptPubKey" : {         (json object)
     "asm" : "code",       (string) 
     "hex" : "hex",        (string) 
     "reqSigs" : n,          (numeric) Number of required signatures
     "type" : "pubkeyhash", (string) The type, eg pubkeyhash
     "addresses" : [          (array of string) array of bitcoin addresses
        "address"     (string) bitcoin address
        ,...
     ]
  },
  "coinbase" : true|false   (boolean) Coinbase or not
}

Examples:

Get unspent transactions
> bitcoin-cli listunspent 

View the details
> bitcoin-cli gettxout "txid" 1

As a json rpc call
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "gettxout", "params": ["txid", 1] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/

- txid (거래아이디)와 vout 숫자(해당거래에서 돈 줄 사람중 몇번째)로 출력정보(UTXO)를 얻을 수 있구나.
- 출력정보안의 scriptPubKey 를 얻는군
- 블럭해쉬도 기본적으로 리턴해주네.

2. 거래 구조 (인용 : 책 '비트코인, 블록체인과 금융의 혁신')

 거래 구조안에는 입력값/출력값/기타등등이 있다.

입력값안에는 해제스크립트가 있고 (근데 출력값 인덱스는 뭐지?)

출력값안에는 잠금 스크립트가 있다.

하나의 거래안의  잠금 스크립트를 해제스크립트로 푸는 건가?  (아니다) 


오호~이전 UTXO를 잠그고 있군? 그럼 이전 거래를 잠그고 현재 거래에서 해제시킨다는 건가?

근데 스크립트가 뭐지?  @.@

쉽게 설명하는 블록체인, 스크립트란 뭔가요? [비트코인의 언어]
쉽게 설명하는 블록체인, 비트코인 거래의 원리와 과정 - 1
쉽게 설명하는 블록체인, 비트코인 거래의 원리와 과정 - 2 [거래의 입력값과 출력값]
비트코인 시스템에서 사용하는 암호 기술(II) - 거래 검증

gettransaction "txid" ( include_watchonly )

Get detailed information about in-wallet transaction <txid>

Arguments:
1. "txid"                  (string, required) The transaction id
2. "include_watchonly"     (bool, optional, default=false) Whether to include watch-only addresses in balance calculation and details[]

Result:
{
  "amount" : x.xxx,        (numeric) The transaction amount in BTC
  "fee": x.xxx,            (numeric) The amount of the fee in BTC. This is negative and only available for the 
                              'send' category of transactions.
  "confirmations" : n,     (numeric) The number of confirmations
  "blockhash" : "hash",  (string) The block hash
  "blockindex" : xx,       (numeric) The index of the transaction in the block that includes it
  "blocktime" : ttt,       (numeric) The time in seconds since epoch (1 Jan 1970 GMT)
  "txid" : "transactionid",   (string) The transaction id.
  "time" : ttt,            (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)
  "timereceived" : ttt,    (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)
  "bip125-replaceable": "yes|no|unknown",  (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);
                                                   may be unknown for unconfirmed transactions not in the mempool
  "details" : [
    {
      "account" : "accountname",      (string) DEPRECATED. The account name involved in the transaction, can be "" for the default account.
      "address" : "address",          (string) The bitcoin address involved in the transaction
      "category" : "send|receive",    (string) The category, either 'send' or 'receive'
      "amount" : x.xxx,                 (numeric) The amount in BTC
      "label" : "label",              (string) A comment for the address/transaction, if any
      "vout" : n,                       (numeric) the vout value
      "fee": x.xxx,                     (numeric) The amount of the fee in BTC. This is negative and only available for the 
                                           'send' category of transactions.
      "abandoned": xxx                  (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 
                                           'send' category of transactions.
    }
    ,...
  ],
  "hex" : "data"         (string) Raw data for transaction
}

Examples:
> bitcoin-cli gettransaction "1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"
> bitcoin-cli gettransaction "1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" true
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "gettransaction", "param=
getrawtransaction "txid" ( verbose )

NOTE: By default this function only works for mempool transactions. If the -txindex option is
enabled, it also works for blockchain transactions.
DEPRECATED: for now, it also works for transactions with unspent outputs.

Return the raw transaction data.

If verbose is 'true', returns an Object with information about 'txid'.
If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.

Arguments:
1. "txid"      (string, required) The transaction id
2. verbose       (bool, optional, default=false) If false, return a string, otherwise return a json object

Result (if verbose is not set or set to false):
"data"      (string) The serialized, hex-encoded data for 'txid'

Result (if verbose is set to true):
{
  "hex" : "data",       (string) The serialized, hex-encoded data for 'txid'
  "txid" : "id",        (string) The transaction id (same as provided)
  "hash" : "id",        (string) The transaction hash (differs from txid for witness transactions)
  "size" : n,             (numeric) The serialized transaction size
  "vsize" : n,            (numeric) The virtual transaction size (differs from size for witness transactions)
  "version" : n,          (numeric) The version
  "locktime" : ttt,       (numeric) The lock time
  "vin" : [               (array of json objects)
     {
       "txid": "id",    (string) The transaction id
       "vout": n,         (numeric) 
       "scriptSig": {     (json object) The script
         "asm": "asm",  (string) asm
         "hex": "hex"   (string) hex
       },
       "sequence": n      (numeric) The script sequence number
       "txinwitness": ["hex", ...] (array of string) hex-encoded witness data (if any)
     }
     ,...
  ],
  "vout" : [              (array of json objects)
     {
       "value" : x.xxx,            (numeric) The value in BTC
       "n" : n,                    (numeric) index
       "scriptPubKey" : {          (json object)
         "asm" : "asm",          (string) the asm
         "hex" : "hex",          (string) the hex
         "reqSigs" : n,            (numeric) The required sigs
         "type" : "pubkeyhash",  (string) The type, eg 'pubkeyhash'
         "addresses" : [           (json array of string)
           "address"        (string) bitcoin address
           ,...
         ]
       }
     }
     ,...
  ],
  "blockhash" : "hash",   (string) the block hash
  "confirmations" : n,      (numeric) The confirmations
  "time" : ttt,             (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT)
  "blocktime" : ttt         (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)
}

Examples:
> bitcoin-cli getrawtransaction "mytxid"
> bitcoin-cli getrawtransaction "mytxid" true
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getrawtransaction", "params": ["mytxid", true] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/

- 개발 트랜잭션안에는 그 트랜잭션과 연관된 block hash가 있다. 결국 UTXO는 이전 TXID 를 가르키기 때문에 UTXO로 블럭을 찾을 수 있다는 것이고, 해당 블럭이 그 UTXO를 가지고있는지는 merkle path를 통해서 알 수 있을 것이다.


3. 블록구조 

- 채굴로 만들어진 블록의 최종 구조
- 이전 블록 해쉬,머클루트등의 블록헤더와 개별거래들을 모아둔 블록바디가 있다.
- 블록해쉬(ID)는 자체 헤더 정보를 이용해서 만들어 진다.
- 그래서  nounce 와 bits 기반으로 블럭마다 채굴을 첨부터 다시 시작해야하지.


getblock "blockhash" ( verbosity ) 

If verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.
If verbosity is 1, returns an Object with information about block <hash>.
If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. 

Arguments:
1. "blockhash"          (string, required) The block hash
2. verbosity              (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data

Result (for verbosity = 0):
"data"             (string) A string that is serialized, hex-encoded data for block 'hash'.

Result (for verbosity = 1):
{
  "hash" : "hash",     (string) the block hash (same as provided)
  "confirmations" : n,   (numeric) The number of confirmations, or -1 if the block is not on the main chain
  "size" : n,            (numeric) The block size
  "strippedsize" : n,    (numeric) The block size excluding witness data
  "weight" : n           (numeric) The block weight as defined in BIP 141
  "height" : n,          (numeric) The block height or index
  "version" : n,         (numeric) The block version
  "versionHex" : "00000000", (string) The block version formatted in hexadecimal
  "merkleroot" : "xxxx", (string) The merkle root
  "tx" : [               (array of string) The transaction ids
     "transactionid"     (string) The transaction id
     ,...
  ],
  "time" : ttt,          (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)
  "mediantime" : ttt,    (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)
  "nonce" : n,           (numeric) The nonce
  "bits" : "1d00ffff", (string) The bits
  "difficulty" : x.xxx,  (numeric) The difficulty
  "chainwork" : "xxxx",  (string) Expected number of hashes required to produce the chain up to this block (in hex)
  "previousblockhash" : "hash",  (string) The hash of the previous block
  "nextblockhash" : "hash"       (string) The hash of the next block
}

Result (for verbosity = 2):
{
  ...,                     Same output as verbosity = 1.
  "tx" : [               (array of Objects) The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 "tx" result.
         ,...
  ],
  ,...                     Same output as verbosity = 1.
}

Examples:
> bitcoin-cli getblock "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getblock", "params": [


4. 블록구조내의 바디 

- 3번 그림의 transaction 이 스택 형태가 아니라 해쉬값의 트리형태로 구성됨. 아래 그림은 뒤짚어 놓은 것이다.
- 2번 거래구조가 아래 그림의 입력값 A 이고, 이것을 해싱해서 나온 값들로 머클트리를 만들어서 블록구조바디에 추가
- 즉 거래 자체 데이터가 블록에 추가되는 것이 아니다.
- 원시거래구조(2번항목) 포맷은 TxID로 만들어지기 위해 이중해쉬된다. 이 txid 로 부터 머클 트리가 구성된다. 

비트코인 개발자 가이드 및 남이 그린 그림들을 토대로 전체적으로 훑어보아도 예상외로 UTXO 와 Transaction 간의 연결고리를 찾기가 힘들다.Transaction 를 위해 UTXO 는 찾을 수 있는데, 그 반대방향은 안보이는데..다시 찾아보자. 내가 TxID 에 집중해서 그런거 같은데 아마도 Address 라는 공통 분모가 있는거 같기도 하고...(이미 주화입마 상태이다.아는 것 같은데 잘 모르겠다.) 

즉 UTXO의 주인의 address 를 얻고, 그 address 를 통해서 과거 거래정보를 얻은 후에 그 TxID 를 통해 머클패스를 구성 할 것이라고 추측할 수도 있겠다 싶었는데... 웬걸 ~

이미 위에서 UTXO - TXID - 블럭해시 간에 연결고리를 발견해 놓고 먼 딴 생각을 한거냐 


위의 이미지를 보듯이, vin (입력값)에는 "이전 거래"와 "몇번째 UTXO인가" 를 가르키는 txid,vout 이 있다. 이 txid 를 매개변수로 GetRawTransaction API 를 호출하여 이전 거래를 찾거나, 그 거래(txid)를 포함하는 블록의 머클트리를 검증한다는 추론이 더 그럴싸 하다. 근데 이렇다고치면 머클패스/머클트리로 검증하는 것은 얼마나 깊이 검증해야 한다는 거냐? 즉 내가 채굴자인데 이 거래를 내 임시풀에 넣기 전에 검증 하는 용도면 채굴자 선에서 끝나는거 아닌가? 만약 임시풀에 넣고 있다가, 채굴에 성공한후에 제대로 된 블록에 넣었다고 치고 이걸 다른 노드들에게 전파 한다고 칠 때, 그 블록의 검증을 채굴,풀노드 말고 SPV 도 하나? SPV 가 하니깐 머클패스가 필요한거니 당연한건가? SPV 한데 머클패스를 보내나? 아니면 SPV 가 트랜잭션을 받으면 주변 노드한테 머클패스 달라고 요청하나?

아직 전체적인 비트코인 와꾸가 선명히 스케치 되기엔 스크립트의 이해도가 떨어져서 그런듯 하니..좀 더 힘을 내보자.
반복 만큼 좋은 것은 없다. 머리를 정돈하기 위해 아주 쉬운 기초적인 내용부터 다시 시작해보자. 


쉽게 설명하는 블록체인, 스크립트란 뭔가요? [비트코인의 언어]
쉽게 설명하는 블록체인, 비트코인 거래의 원리와 과정 - 1
쉽게 설명하는 블록체인, 비트코인 거래의 원리와 과정 - 2 [거래의 입력값과 출력값]
비트코인 시스템에서 사용하는 암호 기술(II) - 거래 검증

그리고 나서 이 글의 애니매이션을 통해 스크립트의 스택연산에 대해 감잡고 Bitcoin protocolfor developer
트랜잭션의 로우포맷에 대해 익힌후 Bitcoins the hard way: Using the raw Bitcoin protocol

비트코인 개발자 가이드의 거래 부분 을 참고로 수동으로 가상거래를 해보는 아래 멋진 글로 스크립트에 대한 이해를 끝장내자.

Part 1: Transaction Basics
Part 2: Building a Transaction By Hand

다시 한번 읽어보니, 이제 감이 잡힌거 같기도 하다. 장님 코끼리 다리 만지기를 했던 것 같다. 


Validation

비트코인에서의 검증 및 확정은 아래와 같다. - 3가지

1. 거래가 일어나고 그 거래에 대한 검증 (노드가 거래풀에 넣기 위함)  - 거래검증
   ㅁ 공개키 검증 ( A 에게 돈을 보낸 것이 맞나)
   ㅁ 공캐기로 서명 검증 (그 거래에 사용된 UTXO의 출처가 확실히 전 UTXO 부터로 인가? 혹시 내용이 변경되지 않았는가? 
   ㅁ coinbase 까지 이전 UTXO 전체 혹은 옵셔널하게 깊이를 체크한다.(라고 누군가 말하던데, 생각해 보면 깊이 1이면 충분하다고 추론한다. 마스터링 비트코인에도 이거에 대한 언급이 없다.) 

2. 노드에 의해 블록이 완성되고 그 블록에 대한 검증  -블럭검증

   ㅁ 채굴하면서 노드들은 다른 곳에서 날라온 블록에 대한 검증을 한다.블록해쉬의 정확성과 깊이를 조사한다.

3. 블록의 높이에 따른 거래 확정 
   

   ㅁ 거래가 포함된 블록이 길이(높이)를 체크한다.  
   

검증시 사용되는 것들 

 A.  LevelDB에 저장된 UTXO set 과 메타데이터 이용
 B.  블록체인 자체 이용

자 마지막 결과 !!! 여기서 머클패스를 던저주는 과정 1,2,3 중 어디일까?

당연히 거래검증 부분이다. UTXO 들에 대한 검증시 이전 UTXO 는 어떤 블록에 들어 있을 것이고, 블록은 단단히 고정되어있다.단단히 고정되어 있는 블록안에 그 UTXO가 포함된것이 확실하다면 그 UTXO 는 신뢰 할 수 있을 것인데, 풀노드라면 그냥 자신의 정보로 검증하고, SPV 일 경우 머클패스를 받기 위해 요청(Push or Pull) 할 것이다.

참 먼길을 걸어왔다. 이러한 여정을 통해서 막힌 속은 어느정도 해소 할 수 있게 되었다. 하지만 이 글은 모두 추측에 기반 하며 틀린 내용도 있을 것이다. 본 글을 사실로 받아드리지 말자. 추후에 내가 직접 C++ 소스를 까보며 정확히 확인 해야 할거 같다. 개발자적인 소명이랄까? 나는 오로지 실제 소스에 기반한 것만 사실이라고 생각한다. 조만간 C++,Golang,Scala 소스등 확인 할 것이며 관련 내용은 블로그에 작성할 것이다.


p.s

* 나중에 발견한 관련 동영상. 

Blockchain/Bitcoin for beginners 7: Blockchain header: Merkle roots and SPV transaction verification




* 마지막으로 머클트리에 먼가 문제가 있다고 느끼는 사람이 있을지도 모르겠다. 그런 사람이 실제 있다.

이 사람인데 --> 


다음 링크를 참조하자.

 https://blog.ethereum.org/2015/11/15/merkling-in-ethereum/




이름에서 느껴지듯이 Economy of Things (이하 EoT) 는 IoT 의 인프라를 이용해서 경제 활동에 이용한다는 개념인데, 블록체인과는 어떤 관계일까? 이것을 이해 하기 위해 먼저 사물인터넷과 블록체인에 대해 간단히 알아보자. 

IoT (사물인터넷)

사물인터넷이란 각 사물들이 인터넷으로 연결 (직접 연결되지 않아도 된다. 보통 게이트웨이등을 통해서 연결됨) 되는 현상을 말하는데, 스마트폰의 확산 + 강력한 센서 + 사물로 부터 나온 빅데이터를 저장 할 수 있는 저장공간의 발전 및 기계 학습을 통한 추가정보생산이라는 저변에 의해 탄생 된 개념이다.
홈의 각 가전들도 사물이 될 수 있고, 공장의 많은 기계들도 사물이 될 수 있으며, 빌딩도 마찬가지이다. 만약 홈,공장,빌딩의 사물들에 대한 에너지를 효율적으로 활용하기 위해 그것들을 인터넷으로 연결해서 인간의 개입 없이 서로 소통하게 하고, 스마트폰을 통해서 인간과 소통하게 한다면 그런 부분에서 여러가지 BM( BEMS,HEMS,FEMS base on IoT) 이 탄생 할 수 있게 된다. 


블록체인

블록체인은 데이터를 분산해서 처리를 하되, 그 데이터에 대한 신뢰를 개인간 서로 믿을 수 있게 만드는 기술이라고 볼 수 있다. (컴퓨터공학적으로 해쉬,비대칭암호화,링크드리스트,P2P 등의 기술을 기반으로한 쿨한 아이디어를 통해 완성한다.) 상호 신뢰를 위해서 기존에는 위임된 기관(은행,부동산중개소,페이팔,마스터카드 등등)를 통해 거래를 해야 했던 구조를 벗어나, (은행등을 유지하기 위한 비용을 없애고) 각자(이 글을 보고 있는 당신)가 해당 기관이 되어 분산된 형태의 신뢰 구축 시스템을 만들기 위한 기술 토대이다. 주체가 있는 프라이빗 블록체인이 있으며 완전히 일반 개인에게 분산 시키는 것을 퍼블릭 블록체인이라고 한다. 주요 특징은 모든 거래에 대한 기록을 담은 전자장부를 모든 개인(정확히는 마이닝 하는 노드)이 공동 소유하여, 거래에 대한 신뢰를 구축 한다는 점이다. 여기서 어느 기간동안의 기록을 담은 장부를 블록이라고 칭하며, 그 다음 기간의 장부들과 서로 연결되어 있기 때문에 블록체인이라고 한다. 

퍼블릭 or 프라이빗 블록체인과 사물인터넷이 결합된 세상을 꿈꿔보는 소프트웨어 개발자로써 지금 당장은 기술에는 설레지만 사회에 초래되는결과에 대해서는 부정적인 입장이다. 신뢰 비용은 낮추되, 개인간의 균형은 기울어지게 될 까봐 우려된다. 많은 지식 공유/토론이 필요하다. 당장은 퍼블릭 블록체인에 대해서는 폐해 및 구현 상 문제점 이 많아 보이고 그렇다고 프라이빗으로 하자니 과연 그게 블록체인 철학에 부합하는 것일까? 하는 의문도 있다.



Economy of Things ??

이제 두가지 개념에 대한 설명을 들었으니, 이걋을 잘 합쳐서 어떤 새로운 개념을 만들 수 있지 않을까 생각하는 사람이 생겨 날 것이라고 추측하는것은 어렵지 않다. 여러분도 지금 바로 생각해 보라.

"사물들 간에 인간의 개입없이 블록체인을 통해 신뢰 기반의 거래를 할 수 있는 인프라" 

이게 떠올랐을 것이며, 나도 마찬가지이다. 이제 앞으로의 글을 통해 우리가 떠올렸을 개념이 다른 사람들도 떠올렸을 개념인지 확인 해 보는 시간을 갖도록 하자. 


실제 말해지고 있는 Economy of Things 들 


IBM

구글을 통해  "Economy of Things" 를 검색해보면 IBM The Economy of Things 이 먼저 나오는데, 싸이트에서 제공하는 PDF의 내용을 살펴보면, 기존 IoT에 의해 조성된 인프라를 경제활동에 참여 시킨다는 아이디어이다. 블록체인은 따로 언급되지 않고 있다. 


Elavon

두번째는 Elavon사 (2011년에 창립된 신용카드 결재 처리 시스템 개발사. 북미 3대 업체의 하나로 매년 처리하는 크레딧 카드 결제 금액은 2000억 달러를 초과하고 있다.) 에서 소개하는 PDF 를 살펴보자. EoT를 실현되는 여정으로 다음과 같이 소개하고 있다.

1. 스마트 기기 출시&확산 (영국가정에 평균 18개의 스마트 디바이스가 있는것으로 조사 되었다)
2. 기기와 경제와의 콜라보레이션 (사고 파는 대상이 넓어지며 행위가 자율적,지능적으로 변화 한다)
3. 신뢰 할 수 있는 거래에 대한 위임 (자신의 회사가 필요하다는 야그~, 하지만 블록체인이라면? 필요 없지 않을까?)
4. 거래 빅데이터 기반의 인공지능 학습에 의한 예측을 통한 진보 

역시 블록체인은 따로 언급되지 않고 있다. 

그리고 다음과 같은 고민을 하고 있다.


Nasdaq 홈페이지 (What Is The Economy of Things?)

nasdaq 홈페이지에서는 위의 IBM의 사례를 들어 설명하고 있다. 즉 IoT 가 물리세계를 디지탈화(digitizing)하는 것이라면, EoT 는 물리세계를 유동화(liquification) 하는 것이다. 

또한 과연 누가 이득을 볼 것인가에 대한 질문을 던지고 있다. 단지 소형칩 제조사?  :-) 


Weeve  https://tokensale.weeve.network/


마지막으로 내가 관심가지고 보고있는 weeve 사다. 이 회사에서는 드디어 블록체인이 언급되었다.
EoT 를 이해하는 3단계 방식  에서는 위에서 설명한 것들을 말하고 있다. 따라서 진부하다.
Weeve 가 EoT 를 말하는 방식  에서는 자신들의 플랫폼을 설명하고 있다.


마지막으로 이회사가 출판한 다음 논문을 소개 해 드린다. 블록체인과 IoT의 만남이다.

https://prismic-io.s3.amazonaws.com/test-eq%2F1a817904-e7fa-4001-b3e0-ec9a20b00c52_whitepaper_technology.pdf


아이디어는 이렇다.


-- 작성중 -- 

+ Recent posts