이더리움을 활용한 IoT 프로젝트 제안 및 프로토타입 구현
이더리움 스마트컨트랙트를 활용한 신뢰받는 주유소 시스템 구축
1. 이 글을 읽기 전에
본 포스팅은 국가암호공모전 및 SK블록체인 해커톤, 한성대학교 공학경진대회에 출품하였던 이더리움 스마트 컨트랙트를 활용한 신뢰받는 주유소 시스템
에 대해 다룹니다. 포스팅의 타겟은 이미 블록체인과 이더리움에 대해 지식이 있고 직접 활용해본 혹은 활용할 예정인 분들입니다. "블록체인을 활용하면 현실 세계의 어떤 문제들을 해결할 수 있을까?"를 고민하는 분들께 도움이 되었으면 합니다.
본 포스팅은 먼저 이상적인 시스템의 구성에 대해 설명하는 파트 그리고 그 중 일부를 구현한 프로토타입에 대해 설명하는 파트 이렇게 2개의 대분류로 이루어져 있습니다. 본 포스팅에서 예시로 드는 정유사는 SK에너지
로 설정하였습니다. (SK에너지
로 설정한 건 단순히 해커톤을 주최한 그룹이라는 점 때문입니다...)
본 작품의 저자는 이현우, 임지훈, 최승주이며 각 저자의 블로그 및 깃허브 링크는 아래와 같습니다. 혹시 연락을 주실 일이 있다면 아래의 경로를 통해 주시면 감사하겠습니다.
- 이현우
Github: https://github.com/dlgusdn616
Medium: https://medium.com/@dlgusdn616
Email: [email protected]
Role: Geth(go-ethereum) 노드 구성 및 운영, 네트워크 및 환경 구축 - 임지훈
Github: https://github.com/heuristicwave
Medium: https://medium.com/@heuristicwave
Email: [email protected]
Role: Node js 백엔드 및 프론트엔드 설계 - 최승주
Github: https://github.com/Bookstore3
2. 개요
오늘날 주유소를 이용하는 소비자들이 가짜 석유 및 조작된 주유량으로 금전적 피해를 보는 사례가 다수 발생하고 있다. 가짜 석유 문제는 석유의 유통 과정에서 각 주유소 지점에 기름이 공급될 때부터 조작이 되어 있는 경우와 주유소 지점에서 자체적으로 시행하는 조작으로 이루어진다.
전자는 주유소 지점의 책임이 아닌 중간 공급업자에 책임이며 후자는 주유소 지점에 책임이 있다. 본 포스팅에서 제시한 프로토타입은 후자의 경우만 예방하고 감독할 수 있다. 전자의 경우를 예방하는 방법은 글의 마지막 부분에서 다루도록 한다.
주유량 조작 문제는 명백하게 주유소 지점에 책임이 있다. 이러한 행위는 보통 계기판에 기름량을 산정하는 프로그램을 조작함으로써 이루어진다. 예를 들어, 어떤 소비자가 경유를 100L 가량 주유받는다고 할 때 소비자는 그에 상응하는 돈을 지불할 것인데 이때 참고할 수 있는 객관적 기준은 주유기에 표시되는 계기판 밖에는 없다. 100L라고 표시는 되지만 사실은 95L가 주유된 것은 아닌지 확인할 수 있는 방법이 현재로서는 존재하지 않는다.
따라서 본 제안에서는 이더리움 블록체인을 활용하여 주유소 지점내에서 이루어지는 불법적인 행위를 감독하고 예방할 수 있는 시스템을 제시한다. 본 시스템이 실제로 상용화된다면, 매년 발생하는 주유소 지점내의 불법적인 행위가 현저히 감소할 것으로 기대가 된다.
시스템의 요지는 주유소 내의 특정 데이터를 스마트 컨트랙트에 지속적으로 반영하여 불법적인 행위가 발생했을 때 바로 확인할 수 있으며 해당 행위가 영구적으로 블록체인에 기록되기 때문에 2차 피해를 예방하고 적절한 조치를 취할 수 있는 것이다.
3. 시스템 제안
3.1 시스템 설계
시스템은 이더리움 합의엔진 중 Clique PoA(Proof of Authority)를 활용하며 다음과 같이 크게 두 가지 요소로 구성이 되어 있다. 정유사(SK 에너지 등) 노드와 가맹 주유소 노드로 구성되어 있으며, 아래 그림은 시스템의 개략적인 구성도이다.
정유사 노드에서는 가맹 주유소 노드들이 전송하는 트랜잭션들을 검증 후 데이터베이스 갱신하는 역할을 담당하고, 가맹 주유소 노드들은 보유하고 있는 탱크의 기름 변동량을 센서로 체크하여 읽어 온 값을 담은 트랜잭션을 발생시키는 역할을 담당한다.
3.1 주유소 노드
주유소 노드의 동작 프로세스를 설명하기 전, 먼저 몇 가지 전제조건을 제시한다. 이는 전국의 모든 주유소가 전부 동일한 형태로 존재하지 않기 때문이다. 시스템의 원활한 설계를 위해 주유소 노드를 다음과 같이 상정하였다. 제안에 예시로 사용되는 기름의 종류는 경유이다.
3.1.1 Tank(경유 탱크)
- 주유소에는 하나의 경유 탱크가 존재한다.
- 하나의 경유 탱크에 연결된 주유기는 3대로, 동 시간대에 총 3대의 차량에 주유가 가능하다.
- 경유 탱크에는 기름의 변동량을 수시로 체크해주는 수위 센서, 가짜 경유인지 진짜 경유인지를 구별해주는 초음파 센서가 설치되어 있다.
- 측정된 센서 값을 이더리움 네트워크로 전송 해주는 역할은 라즈베리파이(Rpi)가 수행하며 라즈베리 파이는 중간에 연결되어 있는 아두이노를 통해 센서값을 읽어온다. 중간에 아두이노를 둔 이유는, 향후 탱크의 개수가 많아질 때 라즈베리 파이를 늘리는 것이 아니라 아두이노만 늘리는 방식으로 설계할 수 있기 때문이다. 즉, 모든 탱크에서 측정되는 데이터는 라즈베리 파이 한 대가 이더리움 네트워크로 전송하게끔 되어 있다.
- 탱크 내의 센서는 하나의 모듈로 구성되어 있다. 한 번 탱크 내에 들어가면 탈부착이 어렵게끔 되어 있으며 이는 센서의 물리적인 조작을 방지하기 위함이다.
- 주유소의 데이터를 반영할 스마트 컨트랙트는 주유소 내 탱크별로 존재한다. 본 제안에서 상정한 모델에 따르면 하나의 주유소에는 하나의 경유 탱크가 존재하므로 주유소 지점당 컨트랙트가 존재한다. 아래의 코드는 실제 적용될 스마트컨트랙트의 생성자 함수이다.
// 생성자가 2개의 매개변수로 주유소 이름(gasStationName)과 탱크가 담고 있는 기름의 종류(gasTankType)을 취하고 있음을 확인할 수 있다.
// 본 제안에서 기름의 종류는 경유(DIESEL)로 통일시킨다.
constructor(string _gasStationName, string _gasTankType) public {
bytes memory isSationNameEmpty = bytes(_gasStationName);
bytes memory isGasTypeEmpty = bytes(_gasTankType);
if (isSationNameEmpty.length == 0 || isGasTypeEmpty.length == 0) {
revert();
}
else {
owner = msg.sender;
gasStation = _gasStationName;
gasTankType = _gasTankType;
errorRange = 5;
}
}
3.1.2 라즈베리파이3(Rpi3)
- 라즈베리파이를 사용한 이유는 이더리움 네트워크에 단순히 트랜잭션만 전송 가능할 정도의 사양을 취하기 위함이다. 굳이 더 좋은 하드웨어를 사용할 필요가 없다.
- 아두이노로부터 시리얼 통신으로 센서 값을 읽어들인다.
- 읽어 들인 센서값을 이더리움 스마트컨트랙트에 기록하는 트랜잭션을 발생시킨다.
3.1.3 주유기(Fuel)
- 탱크에는 3개의 연결된 주유기가 존재한다. 하나의 탱크에서 각 주유기를 통해 경유가 빠져나간다.
- 경유가 빠져나가는 루트는 주유기가 유일하다. 특별한 상황(탱크를 청소해야 하거나, 영업을 위해 부족한 기름을 정유사로부터 공급받는 행위)을 제외하고는 오로지 주유기를 통해서만 경유가 빠져나가야 한다. 그 이외의 경우는 전부 비정상적인 상황이며 곧바로 적절한 조취를 취할 수 있어야 한다.
- 주유기에는 기름이 빠져나간 양을 측정할 수 있는 모듈이 존재한다. 주유가 끝났을 때, 하나의 탱크에 연결된 3대의 주유기로부터 데이터를 받을 수 있다. 라즈베리파이는 탱크 뿐만 아니라 주유기와도 연결되어 있으므로 3대의 주유기로부터 데이터를 각각 가져와 총합한다.
이더리움 스마트 컨트랙트에 기록될 데이터의 종류
- 수위 센서로 측정한 탱크 내 경유의 잔여량 (높이 및 무게로 측정)
- 초음파 센서로 측정한 순수 경유 식별값
- 3대의 측정한 주유량 (라즈베리파이가 합산하여 스마트 컨트랙트에 반영)
- 위 데이터는 트랜잭션이 발생할 때마다 스마트 컨트랙트에 전송되고 모든 값을 다 가지고 있는 것이 아니라 같은 변수에 덮어씌어지는 방식으로 동작한다.
3.2 검증자 노드 (정유사 노드)
3.2.1 노드의 구성
본 제안에서 제시하는 권한 증명 기반 이더리움 프라이빗 네트워크는 2개의 부트 노드와 4개의 검증자 노드로 구성한다. 서버 권역(Availability Zone)은 2개로 구성하였고 각 서버 권역에는 2개의 검증자 노드가 존재한다.
부트 노드는 네트워크 내의 노드들이 서로 인식할 수 있게 해주는 역할을 하고, 검증자 노드들은 주유소 노드에서 발생한 트랜잭션을 블록체인에 반영해주는 역할을 한다. 만약 주유소 노드에서 보낸 트랜잭션에 이상이 감지되면 이벤트 로그가 위조 및 변조 불가능한 기록으로 남게 되고 그 내용을 쉽게 확인할 수 있다.
네트워크를 2개의 권역으로 구성한 것은 하나의 권역이 물리적으로 손상되어도 네트워크 유지에는 이상이 없게 하려는 의도이고 4개의 검증자 노드는 제안의 편의를 위해 설정한 것이다.
설계적인 관점에서 올바른 노드 구성에 대해 설명하기 위해 먼저 잘못된 예시를 제시한 후에 올바른 예시를 제시하려 한다.
2개의 권역 중 하나의 권역이 다운되었을 때도 네트워크가 유지되는 설계가 좋은 설계이고, 하나의 권역이 다운되었을 때 네트워크가 멈추는 건 잘못된 설계다.
잘못된 설계
위의 그림을 보면 4개의 검증자 노드가 모두 다 다르다는 것을 알 수 있다. 다름의 기준은 해당 노드의 계정 주소(EOA, Externally Owned Account)이다. 즉 모두 다른 주소를 가지고 있는 노드로 구성된 것이다.
이렇게 제각기 다른 노드들로 구성을 하면 Clique엔진의 검증자 노드 공식(int(4 / 2) + 1
)에 따라 최소 3개의 노드가 작동 중일 때만 네트워크가 유지된다. 만약 3개 미만의 노드가 동작중이라면, 프라이빗 네트워크에는 더이상 블록이 생성되지 않고 주유소 노드들이 보내는 트랜잭션들이 기록되지 않는다.
즉, 4개의 노드가 모두 다른 계정으로 구성되어 있다면 이는 안전하지 않은 설계일 뿐더러, 권역을 2개로 나눈 의미 역시 무색해진다.
올바른 설계
위의 그림을 보면 4개의 검증자 노드가 존재하는데, 색깔이 2개씩 동일하다는 점을 확인할 수 있을 것이다. 노드가 4개인 것은 맞지만 이 중 2개씩은 같은 EOA(Externally Owned Account)로 구성되어 있다.
이더리움 합의 엔진인 Clique의 검증자 노드 공식(int(2 / N) + 1
)에서 N의 기준은 EOA이기 때문에 아래의 그림에서 물리적인 노드의 수는 4개지만 실제 검증자로 구분되는 노드는 2개이다.
따라서 최소 검증 노드 수는 2개가 되고 둘 중 하나의 권역이 다운되더라도 네트워크는 이상없이 동작할 수 있다.
3.3 작동 매커니즘
실제 블록체인이 주유소에 접목되었을 때의 유즈케이스(Usecase)를 기술한다. 언제 트랜잭션이 발생하여 블록체인에 데이터가 기록되는지가 핵심이고 그 부분은 빨간색으로 기술되어 있다. 이해를 돕기 위해 관련 소스코드 또한 일부를 첨부했다.
먼저 정상적인 루틴에서의 동작을 설명하고 예외가 발생할 수 있는 상황과 그에 따른 대처를 다루었다. 정상적인 루틴 보다는 예외 상황을 어떻게 처리하는지가 신뢰 있는 주유소를 만드는데 있어 더 중요하기 때문에 예외처리 루틴은 매우 중요하고 또 정교하게 설계되어야 한다.
3.3.1 정상적인 루틴
- 빈 탱크에 정유사로부터 경유를 공급받아 탱크의 내용물을 채운다.
- 탱크에 경유 공급이 완료되었다면 이제 정상영업이 시작된다.
- 주유가 필요한 차량이 주유소로 들어온다.
- 주유기에서 주유가 시작된다. 3개가 동시에 주유될 수도 있고 1개의 주유기에서만 주유될 수도 있다.
- 주유기 모듈(Fuel)은 3개의 주유기 모두 주유가 종료될 때까지 대기한다.
- 3개의 주유기에서 모두 주유가 끝났을 때 다음 기술된 세가지 데이터가 연결된 라즈베리파이로 전송된다. 탱크의 수위와 탱크 내 경유의 식별 값을 아두이노가 시리얼 통신을 통해 라즈베리파이로 전송, 주유기 모듈이 탱크와 연결된 3개의 주유기에서 발생한 주유량을 총합하여 그 값을 라즈베리파이로 전송
- 라즈베리파이는 주유기 모듈로부터 받은 총 주유량, 아두이노에서 전송 받은 탱크 내 경유 수위, 경우의 식별 값을 담은 트랜잭션을 발생시킨다.
- 검증자 노드에서는 주유소 노드에서 발생된 트랜잭션들을 모아 블록을 생성하고 반영한다.
요약 3개의 주유기 중 하나라도 주유를 시작할 때 루틴이 시작되며 3대의 주유기가 모두 주유 종료 상태가 되면 라즈베리파이는 블록체인에 관련 데이터를 전송한다.
영업을 하지 않을 때는 약 5분마다 트랜잭션을 생성한다.
3.3.2 예외처리 루틴
예외가 발생하는 경우를 감지할 때는 주유기에서 발생한 주유량 값과 탱크 내 수위 센서로 경유의 양을 측정한 값으로 비교를 한다. 해당 값들만으로 예외 처리가 되지 않는 경우(3.2.3) 에는 경유 식별 값을 추가로 사용하여 경유의 위조 및 변조 여부를 확인한다.
3.3.2.1 주유 전 측정한 탱크 내 경유 잔여량 != 주유량 + 주유 후 탱크 내 경유 잔여량
만약 주유가 끝난 후에 센서로 측정된 경유 탱크의 잔여 경유량 + 주유기에서 빠져나간 경유의 총량이 이전에 측정했던 탱크의 경유량과 다르다면 이는 탱크에 어떤 조작이 가해졌다는 것을 뜻한다.
예를 들어, 원래 100L의 경유가 존재했고 20L를 주유했다면, 남아 있는 경유는 80L가 될 것 이다. 그러나, 예상한 값이 아니라면 탱크에 이상이 발생한것이다.
수위를 측정하는 센서가 언제나 정확한 값을 산출하는 것은 아니기 때문에 오차범위가 존재하고 예외 상황이란 건 허용할 수 있는 오차범위를 넘어설 때를 의미한다.
위와 같은 경우 아래의 두 가지 구체적인 상항을 생각해볼 수 있다.
3.3.2.2 주유 전 측정한 탱크 내 경유 잔여량 > 주유량 + 주유 후 탱크 내 경유 잔여량
주유 전 측정한 탱크 내 경유 잔여량이 100L였고, 주유기에서 빠져나간 주유량이 20L인 상황에서 주유 후 탱크 내 경유 잔여량이 70L인 상황을 생각해보자. 원래라면 80L가 있어야했지만 10L가 부족한 상황이다.
정상적으로 기름이 빠져나가는 건 주유기를 통해서만 가능하므로 이와 같은 상황은 주유기 이외의 다른 방법으로 경유가 탱크에서 빠져나간 것으로 합리적 추론이 가능하다. 따라서 이를 블록체인 네트워크에 기록할 필요가 있다.
관련 스마트 컨트랙트에서 이벤트 로그 기능을 통해 "Tank has lower amount of gas. Possible abnormal gas extraction occurred” 메시지를 블록체인에 기록하여 예외 상황을 기록해 놓는다.
3.3.2.3 주유 전 측정한 탱크 내 경유 잔여량 < 주유량 + 주유 후 탱크 내 경유 잔여량
주유 전 측정한 탱크 내 경유 잔여량이 100L였고, 주유기에서 빠져나간 주유량이 20L인 상황에서 주유 후 탱크 내 경유 잔여량이 90L인 상황을 생각해보자. 원래라면 80L가 있어야했지만 10L가 초과된 상황이다.
있어야할 경유의 양보다 더 많아진 경우이므로 외부에서 경유가 유입되었다는 것으로 판단할 수 있다. 순수하게 경유를 유입한 것인지 아니면 다른 불순물을 섞었는지 판단하기 위하여 이러한 경우 경유 식별 값을 추가로 포함시켜서 트랜잭션을 발생시킨다.
정상적인 상황에 대한 로직 그리고 예외적인 상황인3.3.2.2와 3.3.2.3에 대한 예외처리 로직을 구현한 스마트 컨트랙트(솔리디티) 소스코드는 아래의 코드와 같다.
// beforeFuelAmount: 주유 전 측정한 탱크 내 경유 잔여량
// _filledOutAmount: 주유량
// _sensoredTankAmount: 주유 후 탱크 내 경유 잔여량
// _sensoredGasDensity: 주유 후 경유 식별값
function checkGasTankAmount(uint256 _filledOutAmount, uint256 _sensoredTankAmount, uint256 _sensoredGasDensity) public notFueling notCleaning {
uint256 inputFuelAmount = _sensoredTankAmount.add(_filledOutAmount);
// 오차범위를 반영한 최대 허용치
uint256 maxErrorAmount = beforeFuelAmount.add(errorRange);
// 오차범위를 반영한 최소 허용치
uint256 minErrorAmount = beforeFuelAmount.sub(errorRange);
// 정상적인 루틴: 오차범위 내에서 경유 변동량에 이상이 없는 경우
if(minErrorAmount<= inputFuelAmount && inputFuelAmount <= maxErrorAmount){
//Allows the error range amount.
beforeFuelAmount = _sensoredTankAmount;
emit gasTankCheckedEvent(gasStation, gasTankType, "Tank has correct amount of gas");
}
// 예외 상황 3.3.2.2: 주유 전 측정한 탱크 내 경유 잔여량 > 주유량 + 주유 후 탱크 내 경유 잔여량
// 경유가 정상적인 방법 이외에 유출된 경우가 발생한 상황이다.
else if(inputFuelAmount < minErrorAmount){ //The tank has less fuel than it should
uint256 minAbnormalAmount = beforeFuelAmount.sub( _sensoredTankAmount);
beforeFuelAmount = _sensoredTankAmount;
// 이벤트 로그를 블록체인에 기록
emit abnormalTankCheckedEvent(gasStation, gasTankType, "Tank has lower amount of gas. Possible abnormal gas extraction occured", minAbnormalAmount );
}
// 예외 상황 3.3.2.3: 주유 전 측정한 탱크 내 경유 잔여량 < 주유량 + 주유 후 탱크 내 경유 잔여량
// 경유가 원래 있어야 할 정량보다 많은 경우가 발생한 상황이다.
else { //The tank has more fuel than it should.
uint256 maxAbnormalAmount = _sensoredTankAmount.sub(beforeFuelAmount);
beforeFuelAmount = _sensoredTankAmount;
// 이벤트 로그를 블록체인에 기록
// 불순물이 섞였을 가능성을 생각하여 주유 후 경유 식별값인 _sensoredGasDensity 또한 이벤트 로그에 반영해줘야 한다.
emit abnormalTankCheckedEvent(gasStation, gasTankType, "Tank has higher amount of gas. Possible abnormal liquid mixing occured", maxAbnormalAmount, _sensoredGasDensity );
}
}
예외 처리가 발생했을 때는 이더리움 블록체인에 이벤트 로그가 기록된다. 이로써 각 주유소 지점들을 관리하는 정유사는 어떤 주유소에서 어떤 문제가 발생했는지 파악하고 조치를 취할 수 있다.
3.3.2.4 주유를 하지 않을 때 5분 전 경유 잔여량 != 5분 뒤 경유 잔여량 (오차범위를 감안했을 때)
주유기를 통해 기름이 빠져나가지 안핬는데, 5분 전 경유 잔여량과 5분 뒤 경유 잔여량이 다른 것은 말이 안된다. 주유소 지점 탱크에 어떤 조작이 가해졌음을 확인할 수 있다. 아래의 코드에서 보는 것과 같이 라즈베리파이에서 5분 간격으로 측정을 진행한다. 측정한 값은 곧바로 스마트 컨트랙트로 전송된다.
function refueling(m){
_timerCount[m.machine] = 0;
_timer[m.machine] = setInterval(function(){
let thisMachine = _gasMachine["machine" + m.machine];
_timerCount[m.machine]++;
thisMachine.useLiter = thisMachine.useLiter + 1;
let percent = _timerCount[m.machine]/thisMachine.nowRefuelingAmout;
...
if(_timerCount[m.machine]===thisMachine.nowRefuelingAmout){
clearInterval(_timer[m.machine]);
...
//node send
const url = "http://127.0.0.1:3000/usedgasamount/"+sum;
fetch(url, {
mode: 'no-cors',
method: 'get'
})
.then(function(response) {
response.text().then(function(text) {
return 0;
});
})
.catch(function(error) {
console.log(error);
});
}
}
}, 300000);
3.3.3 허용되는 예외
기름의 변동측정량이 비정상적이지만 이 중에는 분명 허용되는 예외상황도 존재한다. 허용되는 예외상황들은 다음과 같다.
3.3.3.1 빈 탱크에 정유사로부터 경유를 공급 받을 때
탱크 내 경유 총량이 갑작스레 증가하는 상황이지만, 정유사로부터 경유를 공급받는 것이니 문제가 발생하는 상황은 아니다. 경유 공급의 시작과 종료 시점만 정확하게 기록되면 되는 것이다. 구현한 스마트 컨트랙트 소스 코드는 아래와 같다.
function startFueling(uint256 _fillingFuelAmount) external onlyOwner {
Fueling = true;
newFuelAmount = _fillingFuelAmount;
emit FuelingEvent(gasStation, gasTankType, _fillingFuelAmount, "Filling the gas tank");
}
function stopFueling(uint256 _sensoredGasDensity) external onlyOwner {
Fueling = false;
beforeFuelAmount = beforeFuelAmount.add(newFuelAmount);
newFuelAmount = 0;
emit FuelingEvent(gasStation, gasTankType, beforeFuelAmount, _sensoredGasDensity, "Finished filling the gas tank");
}
정유사로부터 경유 공급이 시작될 때는 startFueling
함수가 호출되고 주유소 지점(gasStation
), 탱크 정보(gasTankType
), 공급량(_fillingFuelAmount
)에 대한 정보가 이벤트 로그로 블록체인에 기록된다.
주유 공급이 끝나면 탱크 내 경유의 식별 값을 매개변수로 취하는 stopFueling
함수가 호출된다. 이로써 공급이 완료된 후에 위조 및 변조 되지 않은 경유가 들어왔는지 여부를 확인할 수 있다.
3.3.3.2 탱크를 청소하기 위해 경유를 모두 추출하거나 청소 후 다시 채워 넣을 때
탱크 내 경유 총량이 갑작스레 감소하는 상황이지만, 청소를 위해 경유를 빼는 것이니 문제가 발생하는 상황은 아니다. 관련된 스마트 컨트랙트 코드는 아래와 같다.
function startCleaning(uint256 _sensoredGasDensity) external onlyOwner {
Emptying = true;
EmptyingAmount = beforeFuelAmount;
beforeFuelAmount = 0;
emit CleaningEvent(gasStation, gasTankType, EmptyingAmount, _sensoredGasDensity, "Started cleaning the tank");
}
// 청소 다하고 기름 채워넣었을 때
function stopCleaning(uint256 _sensoredTankAmount, uint256 _sensoredGasDensity) external onlyOwner { //When cleaning is over and the fuel has been recharged.
Emptying = false;
uint256 maxErrorAmount = EmptyingAmount.add(errorRange);
uint256 minErrorAmount = EmptyingAmount.sub(errorRange);
if(minErrorAmount<= _sensoredTankAmount && _sensoredTankAmount <= maxErrorAmount){
beforeFuelAmount = EmptyingAmount;
}
emit CleaningEvent(gasStation, gasTankType, EmptyingAmount, _sensoredGasDensity, "Finished cleaning the tank");
}
허용되는 예외 상황들이 발생했을 때는 이벤트 로그가 블록체인에 기록된다. 허용되는 예외 상황은 계약을 배포한 정유사 본사(SK에너지)에서 이루어지는데 이때 무분별한 사용과 소비자를 기만할 목적으로 사용될 수 없도록 전에 있던 데이터 양을 고스란히 기록하여 이벤트 로그를 발생시킨다.
이로써 해당 메서드를 호출할 때 정유사로서는 신중을 기할 수밖에 없으며 혹여나 남발했을 경우 이더리움 블록체인에 영구적으로 남기 때문에 소비자들이 직접 확인할 수 있을 것이다.
4. 프로토타입
본 제안을 구체화해보고자 탱크 내의 센서의 값을 스마트 컨트랙트에 반영하는 프로토타입을 제작하였다. 시스템의 전반적인 스펙은 다음과 같다.
다만, 프로토타입에서 사용되는 센서는 수위센서뿐이다. 초음파로 경유를 식별하는 센서의 경우 아직 연구중이며 프로토타입에 적합한 유량 식별 센서를 찾기 어려웠기 때문이다. 본 프로토타입의 한계는 경유 탱크의 변동량만 이더리움 블록체인에 기록되게끔 구현되어 있다는 점이다.
또한 구현의 편의성을 위해 Clique PoA(권한 증명) 엔진이 아닌 Ethash PoW(작업 증명) 엔진을 채택했다.
4.1 프로토타입 설계
전체 설계도를 미리 살펴보면 아래의 그림과 같다. 빠른 구현을 위해 하나의 랩톱에서 작업 증명 방식의 프라이빗 블록 체인을 구축했다.
4.2 주유소 노드
프로토타입의 주유소 노드에 사용된 구성은 아래의 그림과 같다. 본 시스템 제안에서 제시했던 구성과의 차이점은 빨간 글씨로 표시되어 있다.
4.2.1 물통(Bottle)
- 주유소에 존재하는 탱크의 역할을 담당하는 물통이다.
- 물통에는 물의 높이를 측정해주는 수위 센서가 존재한다.
- 수위 센서는 아두이노로 측정한 값을 보내고 아두이노는 시리얼 통신으로 라즈베리파이에 값을 전송한다.
실제 탱크 대신 물통을 사용하였으며 센서는 수위 센서 하나만 사용하였다.
아두이노의 회로 구성과 코드는 다음과 같다.
- 수위 센서로 읽어온 값은 A0핀 (노란색 선)을 통해 받아온다.
- 시리얼 통신을 사용하여 읽어온 센서 값을 3초 간격으로 라즈베리파이에 전송한다.
const int WATER_SENSOR_PIN0 = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorVal0 = analogRead(WATER_SOURCE_PIN0);
Serial.print("0 sensor value = ");
serial.println(sensorval0);
delay(3000);
}
4.2.2 주유기(Web)
- 실제 주유소 노드의 탱크에 연결된 3대의 주유기를 웹으로 구현하였다.
- 아래의 그림과 같이 3대의 주유기가 존재하고 각각
START
, END버튼이 존재한다.
START버튼을 클릭함으로써 주유가 시작되고 시간이 지나서 종료가 되거나 직접
END` 버튼을 눌러 종료할 수 있다.
- 금액 혹은 리터를 입력한 뒤
START
버튼을 누르면 주유가 시작되고 파란색 진행 막대가 채워지기 시작한다. - 금액을 입력했을 때는 좌측 상단의 금액 환산표에 따라 리터가 결정된다. 1리터당 1초로 환산하여 주유 시간도 정해진다.
- 막대가 다 채워지면 (정해진 시간이 지나면) 주유가 종료된다. 주유 중에 END 버튼을 눌러도 주유가 종료된다.
- 웹 페이지의 자바스크립트 소스코드에는
Web3
라이브러리를 활용하여 이더리움 네트워크에 트랜잭션을 발생시킬 수 있게끔 되어 있다.
4.2.3 노드(Node)
- 아두이노로부터 전송 받은 센서 값을 스마트컨트랙트에 반영하는 트랜잭션을 발생시킨다.
- 라즈베리파이에서
Web3
모듈을 사용한NodeJs
코드를 동작시킴으로써 작동한다. - 주유기가 구현되어 있는 웹 페이지를 호스팅한다.
4.3 검증자 노드
프로토타입에서의 검증자 노드 구성은 모두 하나의 컴퓨터에서 이루어진다. 위 그림에 표현된 노드들은 각각 하나의 geth 프로세스를 의미한다.
4.3.1 채굴 노드
- 채굴(Mining)을 담당하는 geth 프로세스다.
- 로컬환경에서 발생하는 트랜잭션들을 모아 블록을 생성하고 이더리움 블록체인에 반영한다.
4.3.2 배포 노드
- 탱크(프로토타입에서는 물통)와 관련된 스마트 컨트랙트를 배포하고 관리하는 geth 프로세스다.
- 탱크에서 발생하는 센서 값들을 사용하여 트랜잭션을 발생시킨다.
4.3.3 예비 노드
- 채굴 노드와 배포 노드 둘 중 하나의 노드가 제 기능을 못하게 되었을 때 언제든지 대체할 수 있는 노드를 의미한다.
- 프로토타입에서의 합의 알고리즘은 작업 증명이기 때문에 하나의 네트워크에 속해 있기만 하다면 언제든지 채굴 노드 혹은 배포 노드를 대체할 수 있다.
4.3 프로토타입 동작 설명
4.3.1 주유 시작
- 위 그림의 각 주유기에 원하는 주유량을 입력한 뒤
START
버튼을 눌러 주유를 시작한다. - 진행 막대가 다 채워지거나 3대의 주유기에
END
버튼이 눌렸을 때 주유가 종료된다.
4.3.2 주유 종료
NodeJs
로 작성한 서버 코드에서는 세 대의 주유기에서 모두 주유가 끝났는지 확인하고 3대의 양을 총합한다.- 모두 끝나고 합산이 완료되었다면 이더리움 네트워크에 반영하는 모듈로 합산량을 전송한다.
구현된 코드는 아래와 같다.
let sum = 0,
state = 0;
for( var key in _gasMachine ) {
sum = sum +_gasMachine[key].useLiter;
state = state+_gasMachine[key].state;
}
if(state){
console.log('progress');
}else{
for( var key in _gasMachine ) {
_gasMachine[key].useLiter = 0;
}
console.log('total refueling : '+sum);
//node send
const url = "http://127.0.0.1:3000/usedgasamount/"+sum;
fetch(url, {
mode: 'no-cors',
method: 'get'
})
.then(function(response) {
response.text().then(function(text) {
return 0;
});
})
4.3.3 전송된 주유량과 센서 데이터 값을 스마트 컨트랙트에 반영
- 배포되어 있는 스마트 컨트랙트의 함수를 호출한다. 주유량과 센서 데이터 값을 매개변수로 전달한다.
- 함수를 호추함과 동시에 트랜잭션이 발생한다.
- 발생된 트랜잭션은 채굴 노드에서 블록체인에 반영한다.
코드는 아래와 같다. NodeJs
와 Web3
라이브러리를 사용한 코드다.
server.route({
method:'GET',
path:'/usedgasamount/{value}',
handler:function(request,h) {
console.log("Is in the server.route");
var cntSerial = 0;
var extractedGasAmount = request.params.value;
console.log(extractedGasAmount);
parser.on('data', function(data) {
var length = data.length;
var sensor_value = data.slice(8, length);
var waterHeight = parseInt(sensor_value);
var t0 = "0";
if (cntSerial++ === 0) {
if(data[0] === t0) {
myContract.methods.checkGasTankAmount(extractedGasAmount,waterHeight).send({from: rpiEoa0})
else {
console.log('error');
console.log('data: ' + data);
console.log('data[0] ' + data[0]);
}
}
});
return 0;
}
});
호출한 스마트 컨트랙트의 함수 코드는 아래와 같다. 3.3.2 예외처리 루틴에서 설명된 예외 상황 중 일부가 구현되어 있다. 초음파로 경유를 식별하는 식별 값이 없다는 점 외에는 3.3.2 예외처리 루틴에서 제시 되었던 로직과 동일하다.
// beforeFuelAmount: 주유 전 측정한 탱크 내 경유 잔여량
// _filledOutAmount: 주유량
// _sensoredTankAmount: 주유 후 탱크 내 경유 잔여량
function checkGasTankAmount(uint256 _filledOutAmount, uint256 _sensoredTankAmount) public notFueling notCleaning {
uint256 inputFuelAmount = _sensoredTankAmount.add(_filledOutAmount);
// 오차범위를 반영한 최대 허용치
uint256 maxErrorAmount = beforeFuelAmount.add(errorRange);
// 오차범위를 반영한 최소 허용치
uint256 minErrorAmount = beforeFuelAmount.sub(errorRange);
// 정상적인 루틴: 오차범위 내에서 경유 변동량에 이상이 없는 경우
if(minErrorAmount<= inputFuelAmount && inputFuelAmount <= maxErrorAmount){ //Allows the error range amount.
beforeFuelAmount = _sensoredTankAmount;
emit gasTankCheckedEvent(gasStation, gasTankType, "Tank has correct amount of gas");
}
// 예외 상황 3.2.2: 주유 전 측정한 탱크 내 경유 잔여량 > 주유량 + 주유 후 탱크 내 경유 잔여량
// 경유가 정상적인 방법 이외에 유출된 경우가 발생한 상황이다.
else if(inputFuelAmount < minErrorAmount){ //The tank has less fuel than it should
uint256 minAbnormalAmount = beforeFuelAmount.sub( _sensoredTankAmount);
beforeFuelAmount = _sensoredTankAmount;
// 이벤트 로그를 블록체인에 기록
emit abnormalTankCheckedEvent(gasStation, gasTankType, "Tank has lower amount of gas. Possible abnormal gas extraction occured", minAbnormalAmount );
}
// 예외 상황 3.2.3: 주유 전 측정한 탱크 내 경유 잔여량 < 주유량 + 주유 후 탱크 내 경유 잔여량
// 경유가 원래 있어야 할 정량보다 많은 경우가 발생한 상황이다.
else { //The tank has more fuel than it should.
uint256 maxAbnormalAmount = _sensoredTankAmount.sub(beforeFuelAmount);
beforeFuelAmount = _sensoredTankAmount;
// 이벤트 로그를 블록체인에 기록
emit abnormalTankCheckedEvent(gasStation, gasTankType, "Tank has higher amount of gas. Possible abnormal liquid mixing occured", maxAbnormalAmount );
}
}
비정상적인 상황이 인지되면 이더리움 블록체인에 Event 로그를 기록한다. 이벤트는 아래의 코드에 나와 있는 양식으로 블록체인에 기록된다.
{address: "0xe5006916ad70ca9c440ee74860c3853674bf0e88", blockNumber: 625, transactionHash: "0xf3f25b509a9fbb6b8d2dbc7a37ccdb7960890c952d99d031742d46c65dee6eb7", transactionIndex: 0, blockHash: "0x1fd9b564e1ad9373bc711f5304dbd272e6a366a85c7904285ef863ec2407a089", …}
1. address:"0xe5006916ad70ca9c440ee74860c3853674bf0e88"
2. args:{_gasStationName: "SK_ENERGY", _gasTankType: "DIESEL", _fillingFuelAmount: BigNumber, _statusMessage: "Filling the gas tank"}
3. blockHash:"0x1fd9b564e1ad9373bc711f5304dbd272e6a366a85c7904285ef863ec2407a089"
4. blockNumber:625
5. event:"FuelingEvent"
6. logIndex:0
7. removed:false
8. transactionHash:"0xf3f25b509a9fbb6b8d2dbc7a37ccdb7960890c952d99d031742d46c65dee6eb7"
9. transactionIndex:0
10. __proto__:Object
2번 필드를 살펴보면, “SK_ENERGY” 주유소의 “DIESEL”(경유) 탱크에 “Filling the gas tank” 이벤트가 발생하였음을 확인할 수 있다. 그 아래 필드는 기록되어 있는 블록 번호, 블록 해시, 트랜잭션 해시 등의 정보가 기입되어 있고 결과값으로 나온 정보 중 필요한 내용을 파싱하여 출력해보면 아래와 같다.
- 허용되는 예외 상황(정유사로부터 기름을 공급받을 때)에 대한 이벤트 로그
- 예외 상황 3.3.2.2에 해당하는 이벤트 로그
- 예외 상황 3.3.2.3에 해당하는 이벤트 로그
4.4 프로토타입의 한계 및 개선 방안
경유의 성분 분석을 할 수 있는 초음파 식별 센서를 적용하지 못했다.
식별 센서를 적용하지 못할 경우 다음과 같은 불상사가 발생할 수 있다. 기름 탱크를 청소하기 위해 경유를 전부 뺐다가 청소 후 다시 경유를 채울 때 불순물을 섞어서 채우는 것이 가능하다. 즉, 경유 식별 센서가 없다면 탱크를 완전히 비우고 채울 때 조작이 가능하다.부실한 외부 접근 제한
운영자 geth 노드의 정보 (포트 번호, eNode 정보 등)가 외부로 유출 되었을 시 데이터 조작이 가능하다. PoW 체인이기 때문에 더 많은 블록을 빠른 시간 안에 전파하면 데이터가 갈아 엎어진다.센서에서 측정된 데이터 값을 토대로 판단하는 시스템이기 대문에 센서 자체가 조작되는 것에 대한 보안은 되어 있지 않다.
측정에 사용되는 방법은 단순히 높이를 측정하는 것이므로 경유가 출렁이거나 탱크 자체가 움직이는 상황 등이 발생하면 정확한 측정이 어렵다. 또한 기름의 특성상 온도에 따라 부피가 약간씩 달라지기 때문에 높이만으로 측정하는 방법에는 한계가 있다.
위의 한계점들에 대한 개선 방안은 아래와 같다.
모듈화가 가능한 경유 식별 센서에 대한 연구는 게속 진행중이다. 초음파 뿐만 아니라 나노기술기반 가짜 석유 식별센서 개발 및 상용화를 주제로 한 연구 또한 진행중이다. 직접 개발하거나 연구되고 있는 기술들을 종합하여 센서를 개발하고 향후 탱크에 적용하는 방향으로 문제를 개선할 수 있다.
외부로부터의 허가되지 않은 접근을 제한하기 위해 정유사만의 VPC망 안에 프라이빗 네트워크를 구축함으로써 허가 받지 않은 접근을 방지할 수 있다. (사실 이미 SK에서 자체적으로 운영하고 있는 네트워크가 있다고 하니 이 부분은 크게 걱정하지 않아도 될 것 같다.)
한 번 탱크에 부착된 센서 및 관련 모듈들에 외부 조작을 가할 수 없게끔 결합도가 강하게 설계하는 방법이 있다. 그렇게 하면 센서 자체를 조작해서 탈부착하거나 센서를 무력화시키는 행위 등에 대한 예방이 가능하다. 만약 조작을 하더라도 조작한 기록까지 블록체인에 기록이 되므로 정유사 본사에서 적절한 조치를 취할 수 있다. 즉 감시 및 모니터링은 충분히 이루어질 수 있다.
높이 센서 뿐만 아니라 무게 센서를 함께 차용하는 방안이 있다. 경유가 출렁거려 높이가 달라지더라도 무게는 같기 때문에 더 정확한 측정이 가능하다.
5. 결론
구상한 서비스를 현실에 적용하기에는 아직까지 개선점이 많이 필요한 아이디어지만, 지속적인 연구를 통해 서비스를 개선해간다면 분명 깨끗한 유류 관리 시스템을 만들어낼 수 있을 것이다. 소비자들이 피해받지 않고 믿고 쓸 수 있는 주유소 유통 공급망 시스템이 구축되었으면 하는 바람이다.
6. 보너스: 토큰 이코노미
현재 주유를 하면 자동으로 적립이 되는 시스템이 운영되고 있다. 그러나 블록체인 시스템이 도입되면 다음과 같은 매커니즘도 생각해볼 수 있다.
사용자들이 주유를 받으면 영수증에 사용자가 주유한 내용이 반영되어 있는 트랜잭션의 해시가 QR코드가 적혀있다.
사용자는 해당 QR코드를 인식하면 사용자가 주유한 건에 대한 트랜잭션이 정상적으로 처리가 되었는지 확인할 수 있다.
이상현상이 발생하지 않았음을 확인한 사용자는 안심할 수 있으며 현재 이 블록체인 시스템이 정상운영 되는지를 직접 확인한 검증자가 된다.
검증을 직접한 대가로 정유사에서는 포인트를 제공해준다.
정유사는 위와 같은 서비스를 제공했을 때, 소비자로부터 신뢰를 받을 수 있으며 광고 및 입소문 효과까지 누릴 수 있다. 소비자는 주유가 제대로 되었는지 확인하고 안심할 수 있으며 포인트까지 적립받으니 이는 소비자 및 정유사 모두에게 이득이 되는 WIN-WIN 전략이라 할 수 있다.
7. 사용한 소스코드
관련된 소스코드 링크는 아래와 같다.
https://github.com/dlgusdn616/ethereum-gas-station-project
Congratulations @dlgusdn616! You received a personal award!
Click here to view your Board
Do not miss the last post from @steemitboard:
Congratulations @dlgusdn616! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!