IPFS 서버에 커스텀 도메인 주소 사용하기

Untitled

About

IPFS를 사용하여 파일 및 폴더를 저장하게 되면 https://ipfs.io/ipfs/<CID> 형식의 주소로 데이터가 저장된다. 하지만 CID가 Hash 기반으로 정해지기 때문에 폴더의 경우 폴더 내 데이터가 변경된다면 폴더의 주소 또한 바뀌게 된다.

하지만 데이터가 변경되더라도 고정된 도메인을 사용해야할 경우가 있는데 이런 경우 커스텀 도메인을 적용하여 URL을 고정시킬 수 있다.

본 문서에서는 어떻게 커스텀 도메인을 적용하여 IPFS를 사용할 수 있는지에 대한 방법을 작성하였다.

IPNS

https://docs.ipfs.io/concepts/ipns/

IPFS는 콘텐츠 기반 주소 지정을 사용 합니다. 다른 사람 과 같은 IPFS 주소를 공유하려는 경우 콘텐츠를 업데이트할 때마다 해당 사람에게 새 링크를 제공해야 합니다.

IPNS(InterPlanetary Name System)는 업데이트할 수 있는 주소를 만들어 이 문제를 해결합니다.

다음과 같은 폴더가 IPFS에 업로드 되어 있습니다.

ipfs-folder

위 사진의 폴더에 접근하기 위해서는 https://ipfs.io/ipfs/QmYTqQ2XeJ3XyXf4BGWFYm4o87qGXHgG2MMETSk7PUMZsQ 링크에 접근하여야 하지만 폴더 내의 컨텐츠가 변경된다면 위 주소도 바뀌게 되어 위 주소를 사용하는 사용자들에게 새로운 주소를 알려주어야 합니다.

s

이를 고정된 도메인으로 사용하기 위해서 도메인의 DNS를 변경하여 위 링크를 나의 소유의 도메인으로 접근할 수 있도록 만들어 줍니다.

dns

dns

위 사진과 같이 CNAME과 TXT DNS를 설정해 줍니다.

TXT:
_dnslink.<subdomain>

dnslink=/ipfs/<CID>
----
CNAME:
<subdomain>

gateway.ipfs.io.

위 DNS가 적용이 되었다면 https://ipfs.io/ipfs/QmYTqQ2XeJ3XyXf4BGWFYm4o87qGXHgG2MMETSk7PUMZsQ 링크로 접근 가능하던 IPFS내 폴더가 https://ipfs.io/ipns/ipfs-image.cutewisp.com위 주소로도 접근 가능해집니다.

ss

이후에 컨텐츠가 변경되어 CID가 바뀌는 경우 DNS 설정으로 다시 돌아가 CID만 변경하여 입력해 주면 동일한 도메인으로 해당 데이터를 접근할 수 있게 됩니다.

마무리

이 글에서 ipns를 사용하여 ipfs에 도메인을 적용하여 고정된 URL을 만드는 방법을 소개하였습니다. NFT등을 사용할때 주로 ipfs를 사용하게 되는데 NFT의 컨텐츠를 추가할 경우 컨텐츠가 바뀌어 baseURL이 변경되는 경우가 있을것이다. 이럴 때 baseURL자체를 변경해도 되지만 이런식으록 고정된 도메인을 사용하는 방법도 좋을 것이다.


Truffle을 사용하여 ERC-721 토큰 배포하기

Untitled

About

최근 NFT에 대한 관심이 매우 뜨거워지고 있습니다. 우리가 알고있는 대부분의 NFT는 이더리움 네트워크 상에서 동작하고 있으며 이는 대부분 ERC-721 혹은 ERC-1155 형식의 토큰을 사용하고 있습니다. NFT에 주로 사용되는 ERC-721은 토큰의 수량을 주로 다루는 ERC-20과 다르게 토큰의 소유권(Token ID, Token owner)를 주로 다루고 있기 때문에 각각의 토큰에 대한 소유권을 소유자에게 부여하는 특징을 갖게 됩니다.

본 문서에서는 컴파일 및 배포를 가능하게 해주는 툴인 Truffleopenzeppelin 패키지를 이용해서 이더리움 네트워크에서 사용할 수 있는 ERC-721 토큰을 배포하는 예제를 작성하였습니다.

본 문서에서는 메인넷 및 테스트넷의 이더리움 주소 및 ETH가 있다고 가정하고 진행하였습니다.

Prerequisite

Truffle 설치

아래 명령어로 Truffle을 설치해 줍니다.

npm install -g truffle

프로젝트 생성

Truffle 로 사용 될 폴더를 만들고 그 안에서 truffle init 을 실행하여 초기 구성 파일을 생성합니다.

mkdir truffle-tutorial
cd truffle-tutorial
truffle init

정상적으로 구성이 완료되었다면 다음과 같은 폴더 구조를 갖게 됩니다.

folder

컴파일 및 컨트랙트 적용

여기서 package.json 파일을 만들고 아래 내용을 작성합니다.

{
"name": "erc721-contract",
"version": "1.0.0",
"description": "",
"main": "truffle-config.js",
"directories": {
"test": "test"
},
"scripts": {
"build": "truffle compile",
"start:dev": "truffle migrate --reset",
"start:testnet": "truffle migrate --network ropsten",
"start:mainnet": "truffle migrate --network main",
"flat": "truffle-flattener ./contracts/Token.sol > ./flat/Token_flat.sol",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@openzeppelin/contracts": "^4.3.3",
"@truffle/hdwallet-provider": "^1.7.0",
"eth-gas-reporter": "^0.2.22",
"ganache-cli": "^6.12.2",
"solium": "^1.2.5",
"truffle": "^5.4.21",
"truffle-flattener": "^1.5.0",
"truffle-hdwallet-provider": "^1.0.17",
"truffle-plugin-verify": "^0.5.18",
"web3": "^1.6.1"
},
"devDependencies": {
"@openzeppelin/test-environment": "^0.1.9",
"@openzeppelin/test-helpers": "^0.5.15",
"husky": "^4.2.3",
"lint-staged": "^10.5.3",
"solc": "^0.8.10",
"solidity-coverage": "^0.7.17"
}
}

package.jsonopenzeppelin 패키지 및 여러가지를 사용하기 위한 Dependencies를 포함하고 있습니다.

package.json 작성이 완료 되었다면 yarn install 로 설치를 진행합니다.

이제 실제 토큰 컨트랙트를 작성해야 합니다.

contracts/Token.sol 을 생성한 뒤 열고 아래 내용을 작성합니다.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";

contract Token is 
    ERC721PresetMinterPauserAutoId
{
    constructor() ERC721PresetMinterPauserAutoId("TokenName", "TokenSymbol","baseTokenURI") {
        mint(msg.sender);
    }
}

위 코드는 openzeppelin 컨트랙트 내 ERC-721 프리셋 설정을 이용하여 토큰을 생성하는 컨트랙트 입니다.

위 코드에서 토큰이름, 심볼, URI를 적절히 변경하여 수정합니다.

이제 truffle-config.js 를 수정하여 어디에 배포할 것인지 설정을 작성해야합니다. 본 문서에서는 ropsten 테스트넷을 기준으로 작성하겠습니다.

truffle-config.js를 열고 아래와 같은 내용을 추가합니다.

const HDWalletProvider = require('truffle-hdwallet-provider');

const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();
const infuraKey = fs.readFileSync(".infuraKey").toString().trim();
...
...
networks: {
  ...
  ...
    ropsten: {
      provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/${infuraKey}`),
      network_id: 3,       // Ropsten's id
      gas: 5500000,        // Ropsten has a lower block limit than mainnet
      confirmations: 2,    // # of confs to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },
   ...
   ...
}

위 설정에서 infura를 사용하고 있기 때문에 infura key와 이더리움 네트워크에 배포하기 위한 이더리움 주소의 Private key가 필요합니다. 이를 위해서 상단에 해당 키값을 불러올 수 있게 fs.readFileSync()를 추가하엿습니다

위의 설정을 모두 완료하셨다면 ```truffle migrate –network ropsten` 명령어를 입력하시면 컴파일 및 컨트랙트 트랜잭션 전송이 완료되며 실제 토큰이 생성됩니다.

마무리

이 글에서 truffle을 사용하여 ERC-721 토큰을 만드는 방법을 간단하게 소개하였습니다. 이 글에서는 미리 작성된 preset을 사용하여 토큰을 생성했지만 이러한 기능들을 커스텀하여 만드는것도 가능합니다. 본 문서에 작성된 내용을 구현한 깃허브 주소는 링크 에 가시면 확인하실 수 있습니다.


Express vs Fastify 서버 프레임워크 비교

Express vs Fastify

저는 백엔드 프레임워크로 Nestjs를 사용하고 있습니다. Nestjs 는 Express 혹은 Fastify 기반으로 작성을 할 수 있기 때문에 자연스럽게 Express와 Fastify를 비교하여 어떤 경우에 어떤 것을 취해서 개발을 해야할지 고민이 되었습니다. 이같은 문제를 해결하기 위해선 이 두 프레임워크의 비교가 불가피 하였습니다.

두 프레임워크 모두 기본적으로 TypeScript 대응, 라우터 기능, Validator 기능 등 기본적인 서버 프레임워크가 갖춰야할 사항은 모두 갖추었는데 어느 부분에서 차이가 나는 것일까요?

Express

Untitled

Express는 이전부터 현재까지 쭉 Node.js의 표준처럼 사용 되어진 서버 프레임 워크입니다. 대부분 Node.js 기반으로 서버를 개발한다고 하면 Express를 떠올릴만큼 대중적이며 이 같은 이유로 참고할 자료도 훨씬 방대하고 개발자들도 익숙한 프레임워크 입니다. Fastify와 다르게 비동기 처리를 지원하지 않습니다.

Fastify

Untitled

Fastify는 이름처럼 Express보다 빠른 성능을 강조하는 프레임워크 입니다. Fastify측에서 제공하는 벤치마크에 의하면 간단한 hello world! 오버헤드를 측정하는데 Express보다 월등하게 뛰어난 성능을 보여주고 있습니다.

Benchmark

그러면 실제로 fastify와 express의 성능 벤치마크를 확인하여 성능차이를 확인해 봅시다.

https://github.com/foxifyjs/fastify-benchmarks

Untitled

express와 fastify만 비교해서 살펴본다면 약 2배 정도의 요청을 처리하는 것으로 결과가 나왔습니다.


Lerna를 사용하여 Monorepo 사용해보기

Monorepo & Lerna

Created: September 12, 2021 9:30 AM Tags: Tools Updated: September 12, 2021 1:05 PM

Untitled

About

여러 레포지토리의 dependency를 관리하고 업데이트 하는 것은 시간이 오래 걸리고 또한 연동하는데 에러가 많이 발생할 수 있습니다. 그래서 관리 포인트를 여러 레포지토리에 두는 것이 아니라 하나의 레포지토리(monorepo) 에 둠으로써 관리하는 접근 방식을 사용하기 시작 하였습니다.

이렇게 monorepo구조를 사용함으로써 얻는 장점은 아래와 같습니다.

  • 하나의 레포지토리 내에서 관리 되기 때문에 구조적으로 단순함
  • 여러 프로젝트를 하나의 레포지토리에서 관리하기 때문에 종속성에 대한 관리도 통일하여 진행할 수 있어 종속성 관리에 편함
  • 종속성간 관계를 학습하는 것이 multi-repo구조보다 적어지기 때문에 새로운 개발자가 on-boarding 하는것이 편함

본 문서에서는 Lerna를 사용하여 monorepo 를 구성하는 방법을 소개합니다.

Lerna

Lerna 설치

yarn global add lerna

Lerna를 설치해 줍니다. 아래 명령어로 설치를 확인합니다.

lerna --version

프로젝트 생성

monorepo 로 사용 될 폴더를 만들고 그 안에서 lerna init 을 실행하여 초기 구성 파일을 생성합니다.

mkdir lerna-tutorial
cd lerna-tutorial
lerna init

https://i.imgur.com/7JrupTg.png

정상적으로 구성이 완료되었다면 다음과 같은 폴더 구조를 갖게 됩니다.

https://i.imgur.com/HSL4Pyi.png

여기서 lerna.json 파일을 약간 수정을 해줍니다.

  • npm client 로 yarn을 사용함
  • workspaces를 사용함
  • 버전관리는 각 패키지마다 독립적으로 진행함
{
  "packages": [
    "packages/*"
  ],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "independent"
}

https://i.imgur.com/bydKq9L.png

package.json 에도 workspace 폴더를 알 수 있도록 아래 내용을 추가합니다.

"workspaces": [
   "packages/*"
],

https://i.imgur.com/sWyUIUb.png

Package 추가

기본적으로 lerna create <package명> 을 사용하여 자동으로 패키지를 추가할 수 있습니다.

lerna create playground

https://i.imgur.com/ID03lGC.png

정상적으로 패키지가 추가되었다면 아래와 같이 package 폴더 내에 추가 됩니다.

Untitled

Typescript 설정

여기서 이제 monorepo 상의 모든 패키지에서 typescript를 사용하기 위해서 typescript 종속성을 추가해 줍니다. 이를 위해 루트 폴더의 package.json의 다음과 같은 devDependencies 에 종속성을 추가해줍니다.

yarn add typescript -D
yarn add @types/node -D
yarn add ts-node -D

그리고 루트 폴더의 package.json의 scripts에 다음과 같은 스크립트를 추가합니다.

"start": "lerna run start"

lerna run startmonorepo 상의 모든 패키지에서 yarn start 가 실행되게 합니다.

또한 각 패키지의 package.json 의 scripts에는 다음과 같은 스크립트를 추가하였습니다.

"start": "ts-node lib/playground.ts"

typescript를 사용하기 위해 루트 폴더와 각 패키지 폴더에 tsconfig.json 파일을 추가합니다.

{
    "compilerOptions": {
      "module": "commonjs",
      "declaration": true,
      "noImplicitAny": false,
      "removeComments": true,
      "noLib": false,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "target": "es6",
      "sourceMap": true,
      "lib": [
        "es6"
      ]
    },
    "exclude": [
      "node_modules",
      "**/*.spec.ts"
    ]
   }

또한 각 패키지 폴더에 js로 생성된 파일을 모두 ts로 변환해 줍니다. 모든 과정을 완료했다면 다음과 같은 폴더 구조를 갖습니다.

Untitled

루트 폴더에서 lerna run start 를 실행하면 다음과 같은 결과를 얻게 됩니다.

Untitled

마무리

이 글에서 lerna 를 사용한 monorepo 프로젝트를 생성하는 법을 가볍게 소개하였습니다. 본 문서에서 소개한 명령어는 극히 일부일 뿐이고 더욱 자세한 내용는 lerna git repository 에서 더 자세한 정보를 얻을 수 있습니다.


[C++] AtCoder Beginner Contest 216

AtCoder Beginner Contest 216

결과

submission rating

아직 공식 반영이 되지 않아서 ac-predictor에서 가져왔습니다.

Rating : 740 -> 717 (-23)

풀이

A

a

A - Signed Difficulty

X.Y 를 입력받고 주어진 조건에 따라 출력만 하면 된다.

  • Y가 2 이하일 때 X-

  • Y가 3 이상 6 이하일 때 X

  • Y가 7 이상 9 이하일 때 X+

어짜피 Y는 한자리 수로 고정이므로 10을 곱해서 처리했다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_a
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ld t;
    cin >> t;
    ll tt = (ll)(t * 10);
    ll x = tt / 10;
    ll y = tt % 10;
    if (y <= 2)
        cout << x << "-";
    else if (y <= 6)
        cout << x;
    else
        cout << x << "+";

    return 0;
}
B

b

B - Same Name

성과 이름이 n개 주어진다. 여기서 같은 이름이 존재하면 Yes 아니면 No를 출력하는 문제

이름을 붙여서 정렬하여 같은것이 있나 확인하여 풀었다.

정렬한 뒤 동일한 이름이면 붙어있을 것이기 때문에 그것만 체크해 주었다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_b
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    vector<string> s(n);
    vector<string> t(n);
    vector<string> name(n);
    rep(i, n) {
        cin >> s[i] >> t[i];
        name[i] = s[i] + " " + t[i];
    }
    sort(all(name));
    for (int i = 1; i < n; i++) {
        if (name[i] == name[i - 1]) {
            cout << "Yes";
            return 0;
        }
    }
    cout << "No";

    return 0;
}
C

c

C - Many Balls

Spell A, B가 있는데 A는 가진 공 수에 하나를 더하는 것이고 B는 가진 공 수를 두배로 만든다.

이러한 스펠을 사용해서 공을 n개 만들려고 할 떄 스펠을 사용한 순서대로 작성하는 문제

저는 역으로 n에서 시작해서 2로 나뉘어지면 B, 아니면 A로 해서 0까지 만든 뒤 뒤집어 출력하는 방법을 사용하였습니다.

최대 Spell 사용 갯수가 120으로 제한되어 있지만 10^18을 해도 정답은 길지 않기 때문에 무난하게 풀었습니다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_c
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    vector<char> ans;
    while (n) {
        if (n % 2 == 0) {
            n /= 2;
            ans.push_back('B');
        }
        else{
            n -= 1;
            ans.push_back('A');
        }
    }
    reverse(all(ans));
    for(int i = 0;i<ans.size();i++){
        cout << ans[i];
    }

    return 0;
}