안쓰는 안드로이드 폰을 하드웨어 월렛으로 사용하기.

하드웨어 월렛이 생각보다 비싼데다 대부분의 하드웨어 월렛이 USB에 연결해서 사용하는데 보안상 불안하기도 하고 해서 안쓰는 안드로이드 폰을 하드웨어 월렛으로 사용하는 기능을 스몰렛에 추가 하였습니다.(버전 0.8 이상)

새로운 폰에 스몰렛 키 볼트를 설치 합니다. 설치가 되면 폰은 비행기 모드를 설정하여 모든 외부 접속을 차단 합니다. 이후로도 이 폰은 비행기 모드를 해제할 필요가 없습니다.

접속이 차단된 상태에서 마스터 시드를 새로 만들거나 기존의 시드를 입력합니다. 시드 입력이 완료되면 아래 화면과 같이 메뉴를 열어 계정추가를 합니다.

  

추가된 계정을 터치 하면 위 화면과 같이 QR코드를 생성해서 보여줍니다. 이 QR코드는 터치한 계정의 공개 주소입니다.

이제 이더리움 스몰렛이 설치된 다른 폰에서 아래 화면과 같이 메뉴를 열어 QR코드 스캔을 실행하여 키볼트에 표시된 QR코드를 읽습니다.

 

QR코드가 인식되면 위 화면과 같이 새로운 계정이 등록됩니다. 해당 주소가 키 볼트에서 선택한 주소가 맞는지 확인합니다. QR코드로 읽은 계정은 계정 이름에 [QR]이 표시 됩니다.

이제 이더리움을 전송해 봅니다.

 

받을 주소, 수량, 가스비 등을 입력하고 사인+전송 버튼을 누릅니다. 그러면 화면에 위와 같이 QR코드가 나타납니다. 이것을 이번에는 키볼트가 설치된 스마트폰에서 QR코드 읽기 메뉴를 이용해서 읽습니다.

 

성공적으로 읽으면 위 화면과 같이 나타납니다. “거래승인”을 누릅니다. 그러면 다시 위 화면 우측과 같이 QR코드나 나타납니다. 이 QR코드를 스몰렛에서 다시 읽어 주면 전송이 시작됩니다.

프라이비트 키가 저장된 키 볼트는 비행기 모드로 모든 접속이 차단되어 있고 오직 QR코드만을 이용해서 월렛과 키 볼트가 데이터를 주고 받기 때문에 어떤 형식의 네트웍을 이용한 해킹은 불가능합니다. QR코드를 이용하는 방법은 USB를 이용하는 기존의 하드웨어 지갑 보다도 월등한 보안 방식입니다.

PS. 안쓰는 안드로이드 폰이 없을 경우 오래된 폰을 중고로 구입하거나 새폰의 경우에도 아마존이나 알리 익스프레스에서 찾아 보면 저 사양폰은 $50 정도에 구입이 가능합니다. 월렛으로 사용하는 폰은 키볼트 앱만 실행되면 되고 네트웍이나 기타 기능들을 사용하지 않으므로 사양이 높을 필요가 없습니다.

 

스마트컨트랙트 폰지사기의 결말

333ETH라는 이름으로 개설된 스마트컨트랙으로 하루 3.33%의 이자를 준다는 폰지 사기 컨트랙의 첫번째 시즌이 끝났다. 즉 컨트랙의 잔고가 비었다는 뜻이다.

지난달 9월 3일에 시작하여 대략 44일만에 잔고가 바닥이 났다. 즉 컨트랙 개설이후 14일 이내에 입금한 사람들은은 손해을 보지 않았지만 나머지는 다 손해를 봤다는 이야기이다. 14일차되는 9월 17일 이후에 입금한 사람이 더 많으므로 상당한 사람들이 손해를 봤을 것이다.

몇일전에 333ETH는 새로운 컨트랙을 개설하고 다시 장사를 시작하였다. 과연 학습효과가 어떤 결과를 가져올지 지켜볼 만 하겠다.

스몰렛 커넥터 사용법

스몰렛 커넥터(Smallet Connector)는 메타마스크 크롬익스텐션의 기능을 익스텐션 부분과 지갑 부분을 나눈 프로그램이다. 즉 Dapp와 연동하는 부분은 크롬익스텐션에 두고 지갑의 모든 기능은 안드로이드 앱에서 실행한다. 메타마스크는 개인키(Private Key)를 웹 브라우저에 저장하기 때문에 데스크탑이 해킹 당할 경우 매우 위험하다.

스몰렛 커넥터는 데스크탑에서 결제 요청이 있으면 안드로이드 앱에서 결제 요청을 받고 확인 후 처리하는 방식이다. 당연히 개인키는 안드로이드 앱에서 관리하고 어떤 경우에도 네트웍을 통해 움직이지 않는다. 그러면 설치 방법을 알아보자.

우선 스몰렛 커넥트 익스텐션을 크롬웹스토어에서 설치한다. 설치후 익스텐션 버튼을 클릭하면 아래와 같은 화면이 나온다.

 

첫번째 와면에서 Connect to Smallet 버튼을 누르면 두번째 화면이 나온다. 안드로이드 스몰렛 앱이 필요한데 그림의 “Etereum Smallet Download” 링크를 누르거나 구글플레이스토어에서 다운로드 받는다. 스몰렛 안드로이드 앱 사용법은 이글을 참고하면 된다.

 

이더리움 스몰렛을 실행하여 왼쪽 햄버거 메뉴를 눌러 사이드 메뉴를 열고 지갑접속 메뉴를 실행하면 익스텐션에서 표시된 QR코드를 읽을 수 있다. 이 QR코드를 읽으면 익스텐션과 안드로이드 앱의 연결 설정이 완료된다. 연결이 완료되면 위 화면과 같이 연결된 계좌의 퍼블릭키와 잔고가 표시된다.

이제부터는 메타마스크 사용이 가능한 사이트에서 결제등을 시도해 보면 결제 요청이 안드로이드 앱 쪽으로 오는 것을 확인 할 수 있다. 메타마스크와 같은 기능을 하기 때문에 이 익스텐션을 사용하려면 메타마스크 익스텐션을 꺼 두어야 한다.

 

스마트컨트랙 폰지사기

요 근래 많은 사용자 수가 유입되고 있는 333ETH의 코드를 분석해 보자. 많은 폰지 사기들이 있어 왔지만 이렇게 코드로 확실히 그 모습을 드러내는 경우는 많지 않고 대부분 투자자를 모으지 못하고 끝나는데 333ETH는 출시된지 20일정도된 지금 상당한 자금을 모으고 있다.

컨트랙트 주소는 0x311f71389e3de68f7b2097ad02c6ad7b2dde4c71이다. 이 주소로 0.01 ETH 이상을 보내면 투자는 완료된다. 배당은 이 주소로 0ETH를 전송하면 보내준다. 아래 코드에 나와 있다.

function() public payable {
  // investor get him dividends
  if (msg.value == 0) {
    getMyDividends();
    return;
  }

  // sender do invest
  address a = msg.data.toAddr();
  address[3] memory refs;
  if (a.notZero()) {
    refs[0] = a;
    doInvest(refs); 
  } else {
    doInvest(refs);
  }
}

우선 doInvest를 살펴보자.

function doInvest(address[3] refs) public payable notOnPause balanceChanged {
  require(msg.value >= minInvesment, "msg.value must be >= minInvesment");
  require(address(this).balance <= maxBalance, "the contract eth balance limit");

  uint value = msg.value;
  // ref system works only once for sender-referral
  if (!m_referrals[msg.sender]) {
    // level 1
    if (notZeroNotSender(refs[0]) && m_investors.contains(refs[0])) {
      uint reward = m_refPercent.mul(value);
      assert(m_investors.addRefBonus(refs[0], reward)); // referrer 1 bonus
      m_referrals[msg.sender] = true;
      value = m_dividendsPercent.add(value); // referral bonus
      emit LogNewReferral(msg.sender, now, value);
      // level 2
      if (notZeroNotSender(refs[1]) && m_investors.contains(refs[1]) && refs[0] != refs[1]) { 
        assert(m_investors.addRefBonus(refs[1], reward)); // referrer 2 bonus
        // level 3
        if (notZeroNotSender(refs[2]) && m_investors.contains(refs[2]) && refs[0] != refs[2] && refs[1] != refs[2]) { 
          assert(m_investors.addRefBonus(refs[2], reward)); // referrer 3 bonus
        }
      }
    }
  }

  // commission
  adminAddr.transfer(m_adminPercent.mul(msg.value));
  payerAddr.transfer(m_payerPercent.mul(msg.value));    
  
  // write to investors storage
  if (m_investors.contains(msg.sender)) {
    assert(m_investors.addValue(msg.sender, value));
  } else {
    assert(m_investors.insert(msg.sender, value));
    emit LogNewInvestor(msg.sender, now, value); 
  }
  
  if (m_paysys.mode == Paymode.Pull)
    assert(m_investors.setPaymentTime(msg.sender, now));

  emit LogNewInvesment(msg.sender, now, value);   
  investmentsNum++;
}

앞부분은 referrall에 관한 부분으로 중요하지 않다.(추천인에게 일정 배당을 주는 것) 489, 490 라인에서 관리자가 자기꺼 먼저 챙긴다. 즉 투자자는 24시간이 지난 다음부터 0ETH를 contract로 전송해야 일 3.33%를 받지만 관리자는 투자즉시 17%를 빼간다. Admin이 10%, payer가 7%를 떼간다.

아무튼 이런식으로 돈을 떼가면 금방 자금이 바닥나게 되어 있는데 자금이 바닥날 경우를 살펴보자.

function getMyDividends() public notOnPause atPaymode(Paymode.Pull) balanceChanged {
  // check investor info
  InvestorsStorage.investor memory investor = getMemInvestor(msg.sender);
  require(investor.keyIndex > 0, "sender is not investor"); 
  if (investor.paymentTime < m_paysys.latestTime) {
    assert(m_investors.setPaymentTime(msg.sender, m_paysys.latestTime));
    investor.paymentTime = m_paysys.latestTime;
  }

  // calculate days after latest payment
  uint256 daysAfter = now.sub(investor.paymentTime).div(24 hours);
  require(daysAfter > 0, "the latest payment was earlier than 24 hours");
  assert(m_investors.setPaymentTime(msg.sender, now));

  // check enough eth 
  uint value = m_dividendsPercent.mul(investor.value) * daysAfter;
  if (address(this).balance < value + investor.refBonus) {
    nextWave();
    return;
  }

  // send dividends and ref bonus
  if (investor.refBonus > 0) {
    assert(m_investors.setRefBonus(msg.sender, 0));
    sendDividendsWithRefBonus(msg.sender, value, investor.refBonus);
  } else {
    sendDividends(msg.sender, value);
  }
}

450라인에 나와있다. 더이상 줄돈이 없게 되면 nextWave를 부른다.

function nextWave() private {
  m_investors = new InvestorsStorage();
  changePaymode(Paymode.Push);
  m_paysys.latestKeyIndex = m_investors.iterStart();
  investmentsNum = 0;
  waveStartup = now;
  m_nextWave = false;
  emit LogNextWave(now);
}

이 코드는 컨트랙트 마지막에 있다. 단순하다. 그냥 모든것을 리셋하고 다시 시작한다. 그동안 투자한 사람들은? 그동안 배당받은게 전부이고 다 날린거다.

이 컨트랙트는 나름 의미가 있다. 어느정도 성공하고 있기 때문에 폰지사기가 어떻게 끝나는지 분명한 수치로 아마도 영구히 블록체인에 기록되어 후세에 좋은 연구자료와 귀감이 될 것이다. 현재는 하루 1,000이더 정도 들어오고 500 정도 나가고 있다.

 

도박 수수료 1%

블록체인 도박들중 단순한 형태인 주사위 던지기나 이더롤 같은 도박들은 하우스와 고객간 이길 확율은 동일하게 하고 한번 돈을 걸때 마다 1%씩 하우스 피를 받는다. 1%는 상당히 적어 보이지만 상당한 비용이다.

100원으로 이러한 도박을 계속 한다고 가정해보자. 실제 결과가 아닌 단순 기대치 계산을 해보면

첫판에 100원을 걸면 하우스가 1원을 떼가고 99원이 남는다.(하우스대 고객 승률은 동일하므로 기대치는 하우스 피을 뺀 나머지 전체이다) 다시 99원을 걸면 하우스는 0.99원을 떼간다. 즉 하우스는 배팅액의 총액에서 1%을 떼가게 된다. 베팅 총액을 구해보자.

100 + 100 * 0.99 + 100 * 0.99 * 0.99 + 100 * 0.99^3 …

등비수열이다.

형식의 등비수열의 합은

이므로 이경우 a = 100, r = 0.99 이다

100판을 한다고 하면

100(1-0.99^100)/(1-0.99) = 6339.67658727

이 되므로 배팅 총액은 6340원이고 이때 하우스는 이것의 1%인 63원을 가져가게 되고 도박꾼은 수중에 34원만 남게된다. 1000판을 해보면?

100(1-0.99^1000)/(1-0.99) = 9999.56828753

이고 이경우 하우스가 99.99원을 가져가고 고객은 0.01원 이하의 돈이 남게 된다. 즉 오링이다. 시뮬레이션을 해보면 남은 돈을 다거는 전략으로갈때 100원으로 시작해서 1원이 남게 되는데는 460판 정도 걸린다. 한판에 1분이라고 하면 460분이고 7시간 반 정도 재미있게 놀수 있다. 그렇다면 7시간을 노는데 얼마를 지불하는 것이 적당할까? 그 값이 바로 본인이 게임을 시작할때 투입하기로 결정하는 값이다. 일반 카지노에서도 바카라, 블랙잭 같은 게임은 하우스 승률이 1%정도 높으므로 비슷하게 적용할 수 있을 것이다.

Smallet Connector

대부분의 이더리움 Dapp들은 메타마스크를 표준처럼 사용하고 있다. 즉 결제를 하고 그 결과를 기다리고 하는 작업들을 일반 지갑으로 할 경우 편리성이 떨어져서 실제 운영이 어렵기 때문이다. Dapp을 사용하지 못한다면 이더리움을 구해서 고작 거래소에서 사고 팔고 하는거 말고는 할 것이 별로 없다. 그러니 지갑은 필수적으로 DApp을 지원해야 한다.

그래서 이더리움 스몰렛에서 메타마스크와 호환되는 결제 기능을 만들어 보기로 하였다. 메타마스크는 web3라는 개체를 웹페이지에 삽입하는 방법을 사용한다. 웹 프로그램은 이 개체가 있으면 메타마스크가 있다고 보고 web3에 붙은 API들을 사용하는 것이다.

메타마스크 소스에서 대부분의 UI를 다 없애고 실제 결제 부분을 안드로이드 앱으로 통신하여 앱에서 실제 결제 행위가 이루어지 지도록 하는 것이 구현의 핵심이다.

만들어진 크롬 확장프로그램은 여기에서 설치하면 된다. 스몰렛 커넥트라고 이름 지었다.

설치가 되면 아이콘을 눌러서 팝업을 연다.

   

Connet to Smallet 버튼을 누르면 그림과 같이 QR 코드가 나오는데 이더리움 스몰렛 앱에서 메뉴>지갑접속을 해서 이 QR 코드를 읽는다. 접속이 되면 아래 화면과 같이 계좌번호, 디바이스 이름 등이 나타난다.

모든 준비가 되었다. 이제 Dapp을 실행하여 실전을 해볼때다. 현재 어느정도 활성화된 Dapp은 거래소, 도박, 게임 정도이다. Dapp 종류들은 https://dappradar.com/ 에서 찾아 볼 수 있다. 사용자수나 거래 대금등으로 순위를 매겨서 볼 수 있다. 아직 거래소나 도박 외에는 별로 활성화된 DApp이 없음을 알 수 있다.

https://www.blockasino.com/ 에서 바카라 비슷한 게임을 해보자.

주사위 3개를 던져서 합계를 가지고 결정되는 도박이다. 그림과 같이 원하는 판에다 이더를 걸면된다. 중앙의 3개가 같은 숫자가 나오는 부분에 걸지 않는다면 세개가 같은 숫자가 나오면 하우스가 먹는 방식이다. 그림은 SMALL에다 0.03 이더를 베팅한 경우이다. SMALL은 세개의 주사위 합이 4~10 사이로 나오면 두배를 먹게 된다. 3개가 같은 숫자가 나오는 경우를 제외하면 기대값은 100%이다.

“PLACE BET!” 버튼을 누르면 이더리움 스몰렛 앱에 결제 요청이 오면 모든 실행이 제대로 되고 있는 것이다. 결제를 해주면 그 다음은 알아서 돌아간다. 블록체인에 기록되는데 시간이 걸리므로 한참 기다려야한다. 오래 기다려도 안되면 etherscan.io에서 본인 계정을 확인해 보면 진행상황을 알 수 있다.

이더를 소비하지 않고 테스트를 해보는 방법은 메시지 사인을 보내 보는 것이다. https://smallet.co/contracts2/#/sign-message 에서 테스트로 해 볼 수 있다.

모바일 브라우저에서도 가능한데, 크롬은 모바일 버전에서 확장프로그램을 지원하지 않으므로 크롬 확장 프로그램을 지원하는 Yandex 브라우저를 설치해서 해 볼 수 있다.

도박이니 만큼 절대 많은 돈을 걸지 않도록 주의 한다. 특히 하우스가 떼가는 수수료가 적으것 같지만 반복적으로 베팅할 경우 상당히 큰 금액이라 장기적으로 플레이 하면 당연히 돈을 다 잃게 되어 있다. 컨트랙트에 의해 움직이는 도박은 반칙은 없다. 즉 하우스가 나를 속이지는 않지만 컨트랙트 자체의 룰이 장기적으로 플레이어가 돈을 다 잃는 구조로 되어 있다는 점을 잊어서는 안된다. 거의 모든 가상화폐 도박장이 동일하다. 많은 이더리움 도박장들이 1% 정도의 수수료가 싸다고 하지만 주식이나 가상화폐 거래 수수료가 불과 0.0x % 대인것을 감안하면 매우 비싼 것이다.

10분만에 나만의 ERC20 토큰 발행하기.

앱설치하는 시간 포함 12분 안에 컨트랙트를 생성해서 토큰 발행을 해 보겠습니다. 준비물은 안드로이드 폰입니다. 이 데모는 Ropsten Testnet을 이용했지만 mainnet에서도 거의 다르지 않을 것입니다. 자 그럼 시작해 보겠습니다. 동영상을 선호 하시는 분들은 이 포스팅 끝에 전과정을 담은 동영상이 있으니 참고 하시기 바랍니다.

 

위 사진과 같이 구글플레이 가서 “smallet” 검색하여 “이더리움 스몰렛”을 설치합니다.

 

이더리움 스몰렛을 실행하면 위 화면과 같이 “키 볼트”를 설치 하도록 안내합니다. 키 저장소와 지갑앱이 분리된 이유는 나중에 따로 설명 하겠습니다. 우선은 안내대로 설치를 합니다.

 

처음으로 이더리움 스몰렛을 실행하면 위 화면과 같이 “마스터 시드가 없음” 안내가 나오고 다시 키 볼트를 실행하게 됩니다. 키 볼트에서 패스워드를 입력하면 새로운 마스터 시드를 생성하게 됩니다. 키 볼트는 BIP39 기준에 따라 마스터시드를 이용하여 지갑 키들을 생성하게 됩니다.

 

키 볼트에서 지갑키 생성은 자동으로 됩니다. 이더리움 스몰렛으로 화면이 돌아올때까지 잠깐만 기다리면 됩니다. 이더리움 스몰렛은 네가지 이더리움 네트웍을 지원합니다. 그 중에서 Ropsten Test Network을 선택합니다. Ropsten Test Network을 선택하면 화면 상단 우측에 수도꼭지가 나타납니다. 이것을 누르면 테스트용 이더 한개를 전송 받게 됩니다. 참고로 테스트용 이더는 하루에 한개씩 신청 할 수 있습니다. 신청을 하고 조금 기다리면 이더가 들어옵니다. 입금은 자동으로 갱신되지 않으니 화면을 아래로 당겨서 갱신을 하거나 화면 하단 우측 책읽은 사람 아이콘을 클릭합니다.

 

이더가 들어왔으면 컨트랙트 탭으로 이동하여 “토큰 만들기” 버튼을 터치 합니다. 위 화면처럼 원하는 토큰 심볼, 이름, 소수점 자리수를 입력하고 “생성” 버튼을 누릅니다.

화면이 다시 이더리움 탭으로 자동으로 이동됩니다. 토큰 생성에 필요한 모든 입력이 완료되어 있으므로 “사인&전송” 버튼을 누릅니다. 화면처럼 사인을 할 경우 키 볼트 앱으로 다시 자동으로 넘어가서 거기서 사인을 합니다. 모든 프라이비트 키는 키 볼트가 보관하기 때문입니다. “거래승인”을 누릅니다.

 

거래를 전송하고 블록체인에 기록이 될때까지 기다립니다. 화면과 같은 “전송완료” 안내가 나올때까지 기다립니다. 통상 1분안에 됩니다. “확인”을 누릅니다.

 

위 단계에서 블록체인상 토큰 컨트랙트 생성은 마무리 되었지만 편리를 위해서 지갑에 생성된 토큰과 컨트랙트를 추가하는 과정이 자동으로 진행됩니다. 토큰을 추가하고 컨트랙트도 추가 합니다. 따로 입력할 내용은 없이 모두 자동으로 입력 됩니다. 이제 토큰을 발행해 봅니다. 컨트랙트 탭으로 갑니다.

토큰을 받을 주소를 클립보드에 미리 넣어 둡니다. 데모에서는 현재 계정으로 받기 위해서 좌측 상단 메뉴에서 “받을 주소”를 선택하여  현재 본인 계정을 복사해 두었습니다. 컨트랙트 탭에서 “My First Token” 컨트랙트를 선택합니다.(아마도 이미 선택되어 있을 것입니다.) 컨트랙트 이름 우측의 책읽는 아이콘을 터치 하면 위 화면과 같이 아래쪽에 컨트랙트가 제공하는 함수들이 나열됩니다.

그중에서 “mint”를 선택합니다. mint를 터치하면 위 화면과 같이 토큰을 받을 주소와 수량을 입력하게 됩니다. 수량에 “0” 엄청 많은데, 컨트랙트 생성시 소수점을 18로 줬기 때문에 토큰 1개를 보내려면 “0”을 18개 입력해야 합니다. 위 예제는 1,000개를 보내는 경우 입니다. “0”이 21개입니다. 입력이 다 되었으면 “사인”을 터치 합니다. 컨트랙트 생성때와 마찬가지로 다시 “이더리움” 탭에서 전송하고 사인하고 하면 됩니다.

토큰 탭으로 가보면 토큰이 들어와 있는 것을 확인 할 수 있습니다. 1,000개씩 두번을 보내서 2,000이 되었네요.

이 데모에서 사용된 계정에 대한 정보는 아래 이더스캔 링크에서 모두 보실 수 있습니다.

https://ropsten.etherscan.io/address/0xdAF41A6D6CAA5a2353cc24158C30519c7a36a79a

보시면 이미 소스코드 확인이 모두 되어 있어 소스코드까지 다 보입니다. 따로 확인을 하지 않아도 이렇게 되는 이유는 나중에 다른 기회에 설명을 드리겠습니다.

전과정을 동영상으로 담았습니다.

 

 

Intel rapid storage Raid1 하드 확장

인텔 마더보드에는 Raid 컨트롤러가 포함되어 있다.(아마도 대부분 포함되어 있을 것이다.) 이것을 사용하여 Raid1 설정으로 잘 사용하고 있었는데 두번의 하드디스크 고장으로 하드가 1테라에서 2테라가 되었는데 Raid로 복구하다 보니 여전히 1테라만 사용할수 밖에 없어서 이것을 해결해 보기로 하고 도움말을 찾아 보니 아래 링크에 설명이 잘 되어 있다.

RAID1 볼륨을 늘리는 법

방법은 간단하다. Bios에서 ctrl-I 눌러서 RAID설정에 들어간다음 Reset Disks to Non-RAID 를 선택하여 RAID 디스크를 해제한다. 즉 일반 디스크로 만든다. 둘다 일반으로 만든다. 이과정에서 데이터가 다 지워진다고 경고하는데 RAID1의 경우는 지워지지 않는다. 불안하면 일단 하나만 RAID를 해제한다.

윈도우 부팅이 완료되면 이제 일반 하드가 되었으므로 파티션을 키울수 있다. 파티션을 최대 크기로 키운다.

Intel Rapid Storage 드라이버 및 프로그램을 다운 받는다. 글을 쓰는 현재 최신버전은 16.5.1.1030이다.

설치를 다 하고 RAID설정을 하는데 알수없는 오류가 난다면서 안된다. 이것때문에 5시간을 헤멨다. 시스템 부팅을 20번쯤 했나보다. 아무리 검색해도 원인을 찾을 수가 없다. 이리저리 해보다가 원인을 찾았다. Intel RAID 설정을 하기 위해서 하드디스크 일부를 사용한다. 뭐 당연하다. 그런데 위에서 볼륨을 최대로 늘려 놨기 때문에 RAID설정을 저장할 장소가 없는 것이다. 다시 볼륨을 축소하고 RAID설정을 하니 잘 된다. RAID설정이 되면 볼륨을 다시 최대로 늘리면 된다.

RAID1으로 PC를 사용한 이후로 위에서 이야기 했듯이 하드가 두번이나 깨졌다. 그래도 고장난 하드빼고 새하드 끼우기만 하면 알아서 척척 해준다. 꼭 쓰기를 권장한다.

Firebase, Node server + Android

android studio,

firebase 홈페이지>프로젝트선택>프로젝트설정>일반에서 google-services.json을 다운로드 하여 프로젝트에 추가

project gradle에 아래 내용추가

apply plugin: 'com.google.gms.google-services'
...
implementation 'com.google.firebase:firebase-core:16.0.1'
implementation 'com.google.firebase:firebase-messaging:17.1.0'
implementation 'com.google.firebase:firebase-auth:16.0.2'

android, onStart

FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( new OnSuccessListener<InstanceIdResult>() {
    @Override
    public void onSuccess(InstanceIdResult instanceIdResult) {
        deviceToken = instanceIdResult.getToken();
    }
});

node.js

var admin = require('firebase-admin');
var serviceAccount = require('./smallet-27f86-firebase-adminsdk-7vwqg-b48b172508.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://smallet-27f86.firebaseio.com/'
});

여기서 사용되는 json file은 firebase 홈페이지>프로젝트선택>프로젝트설정>서비스계정>새 비공개 키 생성 에서 만들어야 한다. 위에서 만든 google-services.json을 쓰면 안된다.(이것만 헛갈리고 대부분 그냥 절차대로 하면 됨)

서버에서 메시지를 특정 device에 보낼때는 아래 예제와 같이 한다. message 안의 data부분은 알아서 만들면된다. 중요한건 token이고 token은 위 안드로이드 예제에서 저장된  deviceToken을 사용하면 된다.

function sendNotification(tokenId, _receipt) {
  var message = {
    data: {
      transactionHash: _receipt.transactionHash,
      gasUsed: _receipt.gasUsed + "",
      to: _receipt.to + "",
      status: _receipt.status + ""
    },
    token: tokenId
  };
  (async () => {
    console.log(message);
    admin.messaging().send(message)
      .then((response) => {
        // Response is a message ID string.
        console.log('Successfully sent message:', response);
      })
      .catch((error) => {
        console.log('Error sending message:', error);
      });
  })();
}

안드로이드쪽에서 이 메시지를 수신하려면 서비스를 만들면 되고, 포그라운드이든 백그라운드이든 onMessageReceived는 호출되므로 백그라운드에서는 노티피케이션을, 포그라운드에서는 보여지는 액티비티에서 적절히 처리 하면 되겠다.

public class FirebaseService extends FirebaseMessagingService {
    String TAG = "wallet";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.d(TAG, "From: " + remoteMessage.getFrom());

        // Check if message contains a data payload.
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
            Bundle bundle = new Bundle();
            for (Map.Entry<String, String> entry : remoteMessage.getData().entrySet()) {
                bundle.putString(entry.getKey(), entry.getValue());
            }
            if (MainActivity.isActivityVisible()) {
                Message msg = new Message();
                msg.what = Constants.RECEIVE_TX_RECEIPT;
                msg.setData(bundle);
                MainActivity.mHandle.sendMessage(msg);
            } else {
                sendNotification(bundle);
            }
        }

    }

    private void sendNotification(Bundle data) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtras(data);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);
        String channelId = getString(R.string.default_notification_channel_id);
        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        String title = data.getString("status").equals("true") ? "Transaction Success" : "Transaction Fail";
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId).setSmallIcon(R.drawable.ic_notification)
                        .setContentTitle(title)
                        .setContentText(String.format("to %s", data.getString("to")))
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}

 

 

ropsten testnet용 faucet을 만들다-1

스몰렛 베타 버전을 내려고 보니 테스트를 위한 Ether가 필요한데, ropsten faucet이 잘 동작하지를 않는다. 아무래도 오류인듯 한것이 신청자가 한명도 없을때도 이더를 주지 않는다. 이래서야 테스트를 할 수가 없다.

이리저리 궁리하다가 그냥 테스터 이더를 주는 faucet을 직접하나 만들면 어떨까 싶다. 실제도 아니고 test넷의 채굴 난이도야 얼마나 되겠는가. 까짓거 채굴해서 직접 나눠주는 방법을 해보기로 한다.

geth에서 CPU채굴로 해보니 거의 채굴이 안된다. 역시 POW채굴은 그래픽 카드가 있어야 한다. geth CPU채굴은 간단하다. 여기 모든 예제는 ropsten testnet 기준이다. OS는 ubuntu 16.04 이다.

geth –testnet console
>miner.start(10)

start안에 들어가는 숫자는 채굴을 실행할 thread의 갯수이다. 아무튼 이건 거의 채굴 안된다.

GPU채굴을 하려면 설치를 좀 해야 한다. 먼저 OpenCL부터,

sudo apt-get install nvidia-cuda-toolkit clinfo

clinfo를 실행하여 NVIDIA어쩌구 하는 내용이 쭉 나오면 설치 성공이다.  인터넷에 보면 여러가지 OpenCL설치 방법이 있는데 NVIDA드라이버를 NVIDA사이트에서 받아서 설치하는 방법이 표준 답으로 되어 있는데 내 경우는 이렇게 하니 ubuntu desktop이 망가져 버려서 툴킷만 따로 설치하는 방법으로 하였다.

다음은 채굴기 설치. 채굴기는 cpp-ethereum을 설치하면 그 안에 ethminer가 들어 있다.

sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum

설치가 되었으면 이제 실행을 한다. 메인넷은 ethminer만 있으면 그냥 하면 되는데 testnet은 geth를 사용해서 했다. 다른 방법이 있을지도 모르지만 geth에서 해야 지갑도 쓰기 편하고 여러가지로 편하다.

geth –rpc –rpccorsdomain localhost –datadir /your/geth/datadir –testnet
ethminer -G

이렇게 하면 ethminer가 geth에 rpc로 연결해서 마이닝 결과를 넘겨준다. geth에는 반드시 채굴 보상을 받을 계정을 만들어 둬야 한다. geth에 연결할 일이 있을때는,

geth attach ipc://your/geth/datadir/geth.ipc

이렇게 연결하면 geth console을 열어서 다양한 정보를 관찰할 수 있다.

하룻밤을 돌렸더니 1,000 이더가 모였다. 뭐 이정도면 스몰렛 테스터용 이더 모으는데는 별 문제가 없겠다. 이제 이더 신청 받아서 나눠주는 모듈을 만들어야 하겠다. 이건 파트 2 에서.

메인넷과 ropsten을 같이 동작하게 하려면 포트를 서로 다르게 하면 된다.

geth –rpc –rpcport 8546 –rpccorsdomain localhost –datadir /your/data/dir –port 30304