IPFS IPLD - IPLD-Resolvers

IPLD resolver 는 내부 DAG API 모델인 IPLD 데이터 구조 resolver 입니다:

  • put(node, options, callback): IPLD 데이터 구조를 저장하는 노드
    • node: IPLD 구조 노드
    • options: 다음 중 하나를 포함해야 하는 객체
      • 1) cid - 노드의 CID
      • 2) [hashAlg], [version] 및 format
        • 노드의 CID 를 만드는데 사용
        • 기본 hashAlg 및 version 은 기본 형식에 해당
      • 3) callback: 시그니처가있는 콜백 함수
        • function (err, cid) {}, err은 함수가 실행될 때 반환 될 수 있는 오류
        • cid는 저장 장치 객체의 CID
  • get(cid [, path] [, options], callback): 지정된 노드 CID 또는 경로로 해당 노드를 검색
    • options: 선택 객체

localResolve 는 bool 유형이며 true 이면 로컬 경로만 해석됩니다.

  • callback 은 서명 function (err, result) 가 있는 콜백 함수이며 result 는 다음 내용이 포함된 객체
    • 1) value: get 에 의해 얻어진 데이터
    • 2) remainderPath: 전체 경로가 해석되는지 또는 localResolve 가 선택되는지 여부
    • 3) cid: 마지막으로 발견된 노드 탐색
  • getMany(cids, callback): 동시에 여러 노드 검색
    • callback: 서명된 콜백 function (err, result) 이며 결과는 CID에 해당하는 노드의 배열
  • getStream(cid [, path] [, options]): get과 같지만 노드 (인수)를 전달하는 데 사용되는 소스 pull-stream 을 반환
  • treeStream(cid [, path] [, options]): cid + path 에서 모든 경로는 pull-stream 에 의해 반환
    • options: recursive 부울 유형-lingks 순회
  • remove(cid, callback): 지정된 cid로 노드를 삭제
  • support.add(multicodec, formatResolver, formatUtil): IPLD 자료 구조에 일부 지원 추가
  • support.rm(multicodec): IPLD 에 대한 일부 지원 제거

IPFS IPLD - IPLD-CID

1. 왜 CID가 필요한가?

CID 는 IPFS 분산 파일 시스템에서와 유사한 표준 파일 주소 지정 형식입니다. 콘텐츠 주소 지정, 암호화 해싱 알고리즘 및 자체 설명 형식을 결합합니다. IPFS 및 IPLD 의 중요한 내부 식별자 입니다.

CID 관련 토론은 다음을 참조하십시오: https://github.com/ipfs/specs/issues/130 (첫 번째 게시물은 여기를 참조하십시오)

2. 프로토콜 설명

CID 는 콘텐츠 주소 지정을위한 자가 기술 식별자 입니다. 콘텐츠의 주소를 가져 오기 위해서는 암호화 해시 함수를 사용해야합니다. 많은 multiformats 를 사용하여 유연한 자체 기술을 구현합니다. 즉 해쉬 값을 얻기 위해 multihash 를 사용하고 콘텐츠 유형을 기술하기 위해 multicodec-packed 으로 포장하고, CID 자체를 문자열로 인코딩하는 multibase 를 사용합니다.

현재 버전: CIDv1

CIDv1 은 네 부분으로 구성됩니다.

<cidv1> ::= <mb><version><mcp><mh>
# or, expanded:
<cidv1> ::= <multibase-prefix><cid-version><multicodec-packed-content-type><multihash-content-address>
  • <_multibase-prefix_> 멀티베이스 인코딩 (1 ~ 2 바이트) 입니다. CID를 다른 형식으로 인코딩하는 것이 편리합니다.
  • <_cid-version_> 향후 업그레이드를 용이하게 하기 위해 CID 버전을 나타내는 변수입니다.
  • <_multicodec-packed-content-type_> 콘텐츠 또는 데이터의 유형을 나타 내기 위해 다중 코드로 인코딩 된 형식을 사용하는 형식입니다.
  • <_multihash-content-address_> 다중 해시 값은 콘텐츠의 암호화 해시 해시를 나타냅니다. Multihash 는 CID 가 향후 업그레이드 및 수정을 위해 다른 암호화 해시 기능을 사용할 수있게 합니다.

3. 디자인 컨셉

CID 는 IPFS 를 구축 할 때 여러 가지 장단점을 염두하여 고안되었습니다. 이것은 여러 형식을 지원하는 많은 프로젝트와 관련이 있습니다.

압축성: CID 바이너리 기능으로 압축하는 것이 매우 효율적이며 CID 가 URL 의 일부가 될 수도 있습니다. 운송 편의성: “copy-pastability”. CID 는 멀티베이스 인코딩으로 쉽게 전송됩니다. 예) base58 btc 로 인코딩 된 CID 의 길이는 더 짧아지고 해시 값은 쉽게 복사되고 붙여 넣어집니다. 가변성: CID 는 모든 형식의 결과, 모든 해시 함수를 나타낼 수 있습니다. 콘텐츠 잠금 방지: CID 는 이전 콘텐츠에서 보호해야합니다. 업그레이드 가능성: CID 의 인코딩 된 버전을 업그레이드 할 수 있어야 합니다.

4. 읽을 수 있는 CID 값

더 나은 디버깅과 해석을 위해서는 의미 있고 읽기 쉬운 CID 의 내용이 필요합니다. 일반 CID 는 다음과 같이 “사용자가 읽을 수 있는 CID” 로 변환 할 수 있습니다.

<hr-cid> ::= <hr-mbc> "-" <hr-cid-version> "-" <hr-mcp> "-" <hr-mh>

각 부분은 독자적인 읽을 수있는 내용을 나타냅니다.

  • <_hr-mbc_> 는 사용자가 읽을 수있는 multibase 인코딩 (예: base58btc)
  • <_hr-cid-version_> 은 cidv# 의 버전 (예: cidv1 또는 cidv2)
  • <_hr-mcp_> 는 사용자가 읽을 수있는 multicodec-packed 된 인코딩 (예: cbor)
  • <_hr-mh_> 은 사용자가 읽을 수 있는 multihash (예: sha2-256-256-abcdef0123456789 ...)

예를들어

# TODO example
# example CID
# corresponding human readable CID

5. 버전

5.1 CIDv0

CIDv0 은 이전 버전과 호환되는 버전입니다.

  • multibase 는 항상 base58btc
  • multicodec 는 항상 protobuf-mdag
  • cid-version 은 항상 cidv0
  • multihash 는 다음과 같이 표현됩니다. cidv0 :: = <_multihash-content-address_>

5.2 CIDv1

참조: https://github.com/ipld/cid#how-does-it-work-protocol-description

<cidv1> ::= <multibase-prefix><cid-version><multicodec-packed-content-type><multihash-content-address>

6. 기존 구현

  • go-cid
  • java-cid
  • js-cid
  • rust-cid
  • py-cid

IPFS IPLD - IPLD-Tree

1. IPLD 데이터 모델이란 무엇입니까?

IPLD 데이터 모델은 모든 merkle-dag 에 대해 간단한 JSON 기반 구조를 정의합니다. 또한 일련의 인코딩 된 형식 구조를 정의합니다.

2. 제한 및 비전

다음 제한 사항이 있습니다.

  • IPLD 경로는 모호하지 않아야하며 주어진 경로가 통과하는 방식은 일정해야합니다. (예: 링크 이름 충돌을 피하십시오)
  • IPLD 경로는 전역이어야 하며 다른 언어도 지원해야 합니다. (예: ASCII 대신 UTF-8 사용)
  • IPLD 경로는 UNIX 및 웹 위의 레벨에 있어야 합니다. (/ 를 사용하면 ASCII 시스템 내의 전환이 결정됩니다)
  • 많은 시스템이 JSON 인터페이스를 지원합니다. IPLD에는 JSON 형식을 지원하는 가져 오기 및 내보내기 기능이 있어야합니다.
  • JSON 데이터 모델도 간단하고 사용하기 쉽습니다. IPLD 또한 사용하기 쉬워야합니다.
  • 이를 통해 데이터를 쉽게 정의 할 수 있습니다. IPLD 위에 새로운 데이터 구조를 정의 할 때 많은 배경 지식이 필요하지 않습니다.
  • IPLD는 JSON 데이터 모델을 기반으로 하므로 JSON-LD 를 통해 RDF 및 링크 된 데이터 표준과 호환되어야 합니다.
  • IPLD 직렬화 형식 (디스크, 전송 중)은 빠르고 공간 효율적이어야합니다. (JSON 형식으로 저장할 수 없지만 CBOR 또는 다른 형식이어야 함)
  • IPLD 암호화 해시 해시는 업그레이드 가능해야합니다. (멀티 해시 사용)

다음과 같은 특징이 있습니다.

  • IPLD 는 잘못된 데이터 (예: 불완전한 JSON 저장) 가 없어야 합니다.
  • IPLD를 업그레이드 할 수 있어야 합니다. 예) 디스크에 저장된 더 나은 형식이 표시되면 시스템을 약간의 비용으로 업그레이드 할 수 있습니다.
  • IPLD 객체는 merkle-link 뿐만 아니라 속성을 구문 분석 할 수 있어야 합니다.
  • IPLD 에 의해 정의된 형식은 구현 및 변환이 용이해야 합니다.
  • IPLD 에 의해 정의된 형식은 전체 객체를 얻지 않고도 검색 가능해야 합니다. (CBOR 및 Protobuf 는 이미 그것을 할 수 있습니다)

3. 형식 정의

(JSON과 YML 모두 형식의 형식을 표시하는 데 사용됩니다. 객체의 동등성을 보여주기 위해 둘 다 사용합니다.)

IPLD 데이터 모델의 핵심은 그냥 JSON 입니다. 즉, (a) 일부 기본 유형을 가진 트리 구조 파일, (b) JSON 과 일대일 매핑을 (c) json 에서 사용될 수 있음을 의미합니다

다른 한편으로 (a) 일부 오류를 수정하고, (b) 효율적인 직렬화를 수행하며 (c) 새로운 기술을 기다리는 단일 전송 형식으로 제한되지 않기 때문에 “JSON”이 아닙니다

3.1 Basic Node 단일 노드

다음은 JSON 으로 대표되는 IPLD 객체입니다:

{
  "name": "Vannevar Bush"
}

multihash 값이 QmAAA … AAA 라고 가정합니다. 링크가 없고 문자열만 있습니다. 그러나 사용자는 여전히 keyname 으로 구문 분석 할 수 있습니다.

$ ipld cat --json QmAAA...AAA
{
  "name": "Vannevar Bush"
}

$ ipld cat --json QmAAA...AAA/name
"Vannevar Bush"

또한, yml 구조체에서:

$ipld cat --yml QmAAA...AAA
---
name: Vannevar Bush

$ipld cat --xml QmAAA...AAA
<!xml> <!-- todo -->
<node>
  <name>Vannevar Bush</name>
</node>

3.2 Linking Between Nodes (노드 간 링크)

노드 간의 merkle-link 가 IPLD 의 존재 이유입니다. IPLD 내의 링크는 노드 내에 포함된 특별한 형식일 뿐입니다.

{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // 이전 예제의 데이터에 연결
  }
}

위 데이터의 다중 해시 값이 QmBBB … BBB 라고 가정합니다. 노드에는 QmAAA … AAA 에 대한 subpath 링크 작성자가 있으므로 다음을 수행할 수 있습니다.

$ ipld cat --json QmBBB...BBB
{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // links to the node above.
  }
}

$ ipld cat --json QmBBB...BBB/author
{
  "name": "Vannevar Bush"
}

$ ipld cat --yml QmBBB...BBB/author
---
name: "Vannevar Bush"

$ ipld cat --json QmBBB...BBB/author/name
"Vannevar Bush"

링크를 사용함으로써 IPLD 사용자는 복잡한 데이터 구조를 구축 할 수 있습니다. 따라서 관계 구조를 표시하거나 보조 데이터를 추가하는 것과 같은 정보를 쉽게 인코딩 할 수 있습니다. 그러나 그것은 다음에 논의 될 “대상 링크 협약” 과는 다릅니다. 때로는 다른 객체를 만들지 않고도 링크에 데이터를 추가하기를 원할 때가 있습니다. 실제 IPLD 링크를 다른 객체에 중첩시키고 추가 속성을 사용하면 됩니다.

참고: 링크 속성은 travesal 모호성으로 인해 링크 객체에서 직접 허용되지 않습니다.

예) 파일 시스템이 있고 노드 간의 권한 제어 또는 소유권에 대한 메타 정보를 분배하려고한다고 가정합니다. 해시 값이 QmCCC…CCC 인 객체 디렉토리가 있다고 가정합니다:

{
  "foo": { // link wrapper with more properties
    "link": {"/": "QmCCC...111"} // the link
    "mode": "0755",
    "owner": "jbenet"
  },
  "cat.jpg": {
    "link": {"/": "QmCCC...222"},
    "mode": "0644",
    "owner": "jbenet"
  },
  "doge.jpg": {
    "link": {"/": "QmCCC...333"},
    "mode": "0644",
    "owner": "jbenet"
  }
}

YML 표현:

---
foo:
  link:
    /: QmCCC...111
  mode: 0755
  owner: jbenet
cat.jpg:
  link:
    /: QmCCC...222
  mode: 0644
  owner: jbenet
doge.jpg:
  link:
    /: QmCCC...333
  mode: 0644
  owner: jbenet

데이터 구조에 고유 속성에 대한 설명을 추가하더라도 여전히 구문 분석 할 수 있습니다:

$ ipld cat --json QmCCC...CCC/cat.jpg
{
  "data": "\u0008\u0002\u0012��\u0008����\u0000\u0010JFIF\u0000\u0001\u0001\u0001\u0000H\u0000H..."
}

$ ipld cat --json QmCCC...CCC/doge.jpg
{
  "subfiles": [
    {
      "/": "QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh"
    },
    {
      "/": "QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR"
    },
    {
      "/": "QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3"
    }
  ]
}

$ ipld cat --yml QmCCC...CCC/doge.jpg
---
subfiles:
  - /: QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh
  - /: QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR
  - /: QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3

$ ipld cat --json QmCCC...CCC/doge.jpg/subfiles/1/
{
  "data": "\u0008\u0002\u0012��\u0008����\u0000\u0010JFIF\u0000\u0001\u0001\u0001\u0000H\u0000H..."
}

그러나 link 의 파싱은 순회 (traversal) 에 의해 여전히 얻어 져야 합니다.

3.4 Duplicate property keys (중복 속성 키)

현재 파서의 경우에도 중복 이름이 있는 속성이 없다는 점은 주목할 가치가 있습니다. 따라서 충돌을 피하기 위해 구문 분석 중에 항목의 첫 번째 항목만 읽습니다. 예) 다음과 같은 객체가 있습니다.

{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}

이 순서대로 표준 형식으로 Canonical Format (json이 아닌 cbor) 이 표준 형식으로 나타나며 해당 해시 값은 QmDDD … DDD 입니다. 오직 다음을 얻을 수 있습니다:

$ ipld cat --json QmDDD...DDD
{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}

$ ipld cat --json QmDDD...DDD/name
"J.C.R. Licklider"

3.5 Path Restrictions (경로 제한)

UNIX 와 WEB 에서는 경로 설명에 몇 가지 중요한 문제가 있습니다. (관련 토론도 있습니다) 유닉스와 웹의 모델과 호환되기 위해서 IPLD 는 특정 경로 구성 요소를 가진 경로를 명시적으로 비활성화 합니다. 데이터 자체에는 여전히 이러한 속성이 포함될 수 있습니다. (누군가가이를 수행하고 합법적인 용도로 사용함) 따라서 경로 해석기만 이러한 경로를 통해 구문 분석 할 수 없습니다. 이러한 제한 사항은 일반적인 Unix 및 UTF-8 경로 시스템과 동일합니다.

3.6 Integers in JSON

IPLD 는 JSON 과 직접 호환되며 JSON 의 성공을 최대한 활용할 수 있지만 JSON 오류와 관련이 없습니다. 형식 기본 설정을 따를 수 있지만 항상 잘 정의된 1:1 매핑이 있는지 주의해야 합니다. 정수와 관련하여 JSON 에는 EJSON 과 같이 정수를 나타내는 다양한 문자열이 있습니다. 이들을 사용할 수 있으며 다른 형식의 변환은 자연스럽게 이루어져야 합니다. 즉 JSON 을 CBOR 로 변환 할 때 EJSON 정수는 문자열 값으로 나타내는 대신 자연스럽게 적절한 CBOR 정수로 변환되어야 합니다.

3.7 데이터 구조 예

IPLD 는 사용자가 신규 또는 가져온 이전 데이터 파일을 정의하는 것을 방해하지 않는 간단하고 유연한 형식입니다. 이를 위해 아래에 몇 가지 샘플 데이터 구조를 보겠습니다.

Unix 파일 시스템

A small File (작은 파일)

{
  "data": "hello world",
  "size": "11"
}

A Chunked File 일부

{
  "size": "1424119",
  "subfiles": [
    {
      "link": {"/": "QmAAA..."},
      "size": "100324"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345",
      "repeat": "10"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345"
    },
  ]
}

A Directory

{
  "foo": {
    "link": {"/": "QmCCC...111"},
    "mode": "0755",
    "owner": "jbenet"
  },
  "cat.jpg": {
    "link": {"/": "QmCCC...222"},
    "mode": "0644",
    "owner": "jbenet"
  },
  "doge.jpg": {
    "link": {"/": "QmCCC...333"},
    "mode": "0644",
    "owner": "jbenet"
  }
}

git blob 데이터 블록

{
  "data": "hello world"
}

git tree 구조

{
  "foo": {
    "link": {"/": "QmCCC...111"},
    "mode": "0755"
  },
  "cat.jpg": {
    "link": {"/": "QmCCC...222"},
    "mode": "0644"
  },
  "doge.jpg": {
    "link": {"/": "QmCCC...333"},
    "mode": "0644"
  }
}

git commit 구조

{
  "tree": {"/": "e4647147e940e2fab134e7f3d8a40c2022cb36f3"},
  "parents": [
    {"/": "b7d3ead1d80086940409206f5bd1a7a858ab6c95"},
    {"/": "ba8fbf7bc07818fa2892bd1a302081214b452afb"}
  ],
  "author": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "committer": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "message": "Merge pull request #7 from ipfs/iprs\n\n(WIP) records + merkledag specs"
}

Bitcoin Block

{
  "parent": {"/": "Qm000000002CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8"},
  "transactions": {"/": "QmTgzctfxxE8ZwBNGn744rL5R826EtZWzKvv2TF2dAcd9n"},
  "nonce": "UJPTFZnR2CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8"
}

Bitcoin Transaction

---
inputs:
  - input: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 100
outputs:
  - output: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 50
  - output: {/: QmbcfRVZqMNVRcarRN3JjEJCHhQBcUeqzZfa3zoWMaSrTW}
    amount: 30
  - output: {/: QmV9PkR2gXcmUgNH7s7zMg9dsk7Hy7bLS18S9SHK96m7zV}
    amount: 15
  - output: {/: QmP8r8fLUnEywGnRRUrHB28nnBKwmshMLiYeg8udzYg7TK}
    amount: 5
script: OP_VERIFY

IPFS IPLD - merkle-path

1. merkle-path 란 무엇입니까?

merkle-path 는 유닉스 스타일 경로 (예: /a/b/c/d) 를 사용하여 merkle-link 를 통해 모든 객체를 가져옵니다.

공통 파일 시스템은 IPFS 위에 객체 모델로 설계되어 데이터 객체 작업 및 쿼리를 구현하는 특정 알고리즘을 설계 할 수 있습니다.

2. merkle-path 의 작동 원리는 무엇입니까?

merkle-path 는 유닉스 스타일의 경로로 경로를 점진적으로 내용을 파싱합니다. 내용을 파싱하는 것은 merkle-link 의 내용을 가져 와서 더 파싱하는 것을 의미합니다.

예) 다음과 같은 merkle-path 가 있다고 가정합니다.

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c/d

의미:

  • ipfs 는 컴퓨터가 무엇을 해야할지 구분할 수 있게 해주는 프로토콜 네임 스페이스 입니다.
  • QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k 는 암호화 해시 값입니다.
  • a/b/c/d 는 Unix 경로입니다.

/로 표현되는 통과 가능한 경로는 두 종류의 링크를 나타낼 수 있습니다.

  • 동일한 대상 내부에서 데이터를 쿼리합니다.
  • 객체 간의 정보 탐색은 merkle-link 에 따라 구현됩니다.

예를 들어 다음 데이터 집합이 있다고 가정합니다.

$ ipfs object cat --fmt=yaml QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k
---
a:
  b:
    link:
      /: QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
    c: "d"
    foo:
      /: QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE

$ ipfs object cat --fmt=yaml QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
---
c: "e"
d:
  e: "f"
foo:
  name: "second foo"

$ ipfs object cat --fmt=yaml QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE
---
name: "third foo"

다음과 같은 path 가 있다고 가정:

  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c
    • 첫 번째 객체만 탐색하고 문자열 d 를 가져옵니다.
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/c
    • 두 객체를 두루 거치고 문자열 e 를 얻습니다.
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/d/e
    • 두 객체를 두루 거치고 문자열 f 를 얻습니다.
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/foo/name
    • 첫 번째, 두 번째 대상을 두루 거쳐 문자열 second foo 를 얻게 됩니다.
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/foo/name
    • 첫 번째, 두 번째 대상을 두루 거쳐 문자열 third foo 를 얻게 됩니다.

IPFS IPLD - merkle-dag

merkle-link 가 있는 객체는 방향성 지도 (Merkle-graph) 를 형성하며 암호화되지 않은 상태로 계산 될 수 있습니다. (암호화 된 해시 함수의 속성이 변경되지 않은 경우, 즉 merkle-dag) 그러므로 merkle-linking (merkle-graph) 을 사용하는 모든 그래프는 또한 비순환 그래프 (DAG) 를 지향해야하므로 merkle-dag 가 되어야합니다.