하이퍼레저 자바 sdk로 연동하기

개발개발·2022년 7월 14일
0

블록체인 관련

목록 보기
2/4

지난번에 wsl2 환경에서 하이퍼레저를 실습했다. docker에서 잘 돌아가는 것을 확인했으니 sdk를 이용해서 연동해볼 예정이다.

https://github.com/hyperledger/에 들어가서 어떻게 진행하면 되는지 놀라울 정도로 친절하게 알려주고 있다. 이번 글의 내용은 github에서 본 내용과 fabric-sample repo를 참조해서 진행했다.

폴더 위치는 hyperledger/fabric-samples/test-network 에서 시작한다.
1. 지난 번에 설치했던 내용들 처럼 다시 hyperledger를 실행한다.
2. 채널을 생성하고 체인코드를 배포한다.


2번까지 진행하고 나면
fabric-samples/test-network/organizations 경로에 두개의 디렉토리가 생성된다. 여기서 peerOrganizations를 로컬에 받아 둔다. 하이퍼레저에 요청을 보내기 위해서는 등록된 사용자라는 것을 인증해야 한다. 이때 사용되는 인증서들이 저장된 디렉토리이다.

wsl2 환경이 아닌 로컬 환경에서 이클립스를 이용해 진행할 예정이다. 그러므로 hyperledger sample 파일을 받아보자. 파일은 fabric-samples/에 동봉되어 있으니 ftp를 이용하거나 https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic 에서 다운받으면 된다.

경로가 친숙하게 느껴질수도 있는데 채널에 배포한 chaincode가 있는 경로가 이곳이다. 여기서 application-gateway-java를 받아서 진행했다.

로컬 개발 환경에는 이제 두개의 폴더가 있고 wsl2에서는 체인코드가 배포된채 하이퍼레저가 실행중이다. 이제 gradle 프로젝트를 import 해보자.

이제 App.java파일을 조금만 수정하면 된다.

public final class App {
	private static final String mspID = "Org1MSP";
	private static final String channelName = "mychannel";
	private static final String chaincodeName = "basic";

	// Path to crypto materials.

	// 사용자 인증을 위한 인증서를 가져오기 위한 경로를 수정해야 한다.
	//private static final Path cryptoPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com");
	private static final Path cryptoPath = Paths.get("C:","class","HF","peerOrganizations","org1.example.com");
	// Path to user certificate.
	private static final Path certPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "signcerts", "cert.pem"));
	// Path to user private key directory.
	private static final Path keyDirPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "keystore"));
	// Path to peer tls certificate.
	private static final Path tlsCertPath = cryptoPath.resolve(Paths.get("peers", "peer0.org1.example.com", "tls", "ca.crt"));

	// Gateway peer end point.
	private static final String peerEndpoint = "localhost:7051";
	private static final String overrideAuth = "peer0.org1.example.com";

	private final Contract contract;
	private final String assetId = "asset" + Instant.now().toEpochMilli();
	private final Gson gson = new GsonBuilder().setPrettyPrinting().create();

	public static void main(final String[] args) throws Exception {
		// The gRPC client connection should be shared by all Gateway connections to
		// this endpoint.
		ManagedChannel channel = newGrpcConnection();

		Gateway.Builder builder = Gateway.newInstance().identity(newIdentity()).signer(newSigner()).connection(channel)
				// Default timeouts for different gRPC calls
				.evaluateOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
				.endorseOptions(CallOption.deadlineAfter(15, TimeUnit.SECONDS))
				.submitOptions(CallOption.deadlineAfter(5, TimeUnit.SECONDS))
				.commitStatusOptions(CallOption.deadlineAfter(1, TimeUnit.MINUTES));

		try (Gateway gateway = builder.connect()) {
			new App(gateway).run();
		} finally {
			channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
		}
	}

	private static ManagedChannel newGrpcConnection() throws IOException, CertificateException {
		Reader tlsCertReader = Files.newBufferedReader(tlsCertPath);
		X509Certificate tlsCert = Identities.readX509Certificate(tlsCertReader);

		return NettyChannelBuilder.forTarget(peerEndpoint)
				.sslContext(GrpcSslContexts.forClient().trustManager(tlsCert).build()).overrideAuthority(overrideAuth)
				.build();
	}

	private static Identity newIdentity() throws IOException, CertificateException {
		Reader certReader = Files.newBufferedReader(certPath);
		X509Certificate certificate = Identities.readX509Certificate(certReader);

		return new X509Identity(mspID, certificate);
	}

	private static Signer newSigner() throws IOException, InvalidKeyException {
		Path keyPath = Files.list(keyDirPath)
				.findFirst()
				.orElseThrow(null);
		Reader keyReader = Files.newBufferedReader(keyPath);
		PrivateKey privateKey = Identities.readPrivateKey(keyReader);

		return Signers.newPrivateKeySigner(privateKey);
	}

	public App(final Gateway gateway) {
		// Get a network instance representing the channel where the smart contract is
		// deployed.
		Network network = gateway.getNetwork(channelName);

		// Get the smart contract from the network.
		contract = network.getContract(chaincodeName);
	}

	public void run() throws GatewayException, CommitException {
		// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
		initLedger();

		// Return all the current assets on the ledger.
		getAllAssets();

		// Create a new asset on the ledger.
		createAsset();

		// Update an existing asset asynchronously.
		transferAssetAsync();

		// Get the asset details by assetID.
		readAssetById();

		// Update an asset which does not exist.
		updateNonExistentAsset();
	}
	
	/**
	 * This type of transaction would typically only be run once by an application
	 * the first time it was started after its initial deployment. A new version of
	 * the chaincode deployed later would likely not need to run an "init" function.
	 */
	private void initLedger() throws EndorseException, SubmitException, CommitStatusException, CommitException {
		System.out.println("\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger");

		contract.submitTransaction("InitLedger");

		System.out.println("*** Transaction committed successfully");
	}

	/**
	 * Evaluate a transaction to query ledger state.
	 */
	private void getAllAssets() throws GatewayException {
		System.out.println("\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger");
		
		byte[] result = contract.evaluateTransaction("GetAllAssets");
		
		System.out.println("*** Result: " + prettyJson(result));
	}

	private String prettyJson(final byte[] json) {
		return prettyJson(new String(json, StandardCharsets.UTF_8));
	}

	private String prettyJson(final String json) {
		JsonElement parsedJson = JsonParser.parseString(json);
		return gson.toJson(parsedJson);
	}

	/**
	 * Submit a transaction synchronously, blocking until it has been committed to
	 * the ledger.
	 */
	private void createAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
		System.out.println("\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments");

		contract.submitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300");

		System.out.println("*** Transaction committed successfully");
	}

	/**
	 * Submit transaction asynchronously, allowing the application to process the
	 * smart contract response (e.g. update a UI) while waiting for the commit
	 * notification.
	 */
	private void transferAssetAsync() throws EndorseException, SubmitException, CommitStatusException {
		System.out.println("\n--> Async Submit Transaction: TransferAsset, updates existing asset owner");
		
		SubmittedTransaction commit = contract.newProposal("TransferAsset")
				.addArguments(assetId, "Saptha")
				.build()
				.endorse()
				.submitAsync();

		byte[] result = commit.getResult();
		String oldOwner = new String(result, StandardCharsets.UTF_8);

		System.out.println("*** Successfully submitted transaction to transfer ownership from " + oldOwner + " to Saptha");
		System.out.println("*** Waiting for transaction commit");
		
		Status status = commit.getStatus();
		if (!status.isSuccessful()) {
			throw new RuntimeException("Transaction " + status.getTransactionId() +
					" failed to commit with status code " + status.getCode());
		}
		
		System.out.println("*** Transaction committed successfully");
	}

	private void readAssetById() throws GatewayException {
		System.out.println("\n--> Evaluate Transaction: ReadAsset, function returns asset attributes");
		
		byte[] evaluateResult = contract.evaluateTransaction("ReadAsset", assetId);
		
		System.out.println("*** Result:" + prettyJson(evaluateResult));
	}

	/**
	 * submitTransaction() will throw an error containing details of any error
	 * responses from the smart contract.
	 */
	private void updateNonExistentAsset() {
		try {
			System.out.println("\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error");
			
			contract.submitTransaction("UpdateAsset", "asset70", "blue", "5", "Tomoko", "300");
			
			System.out.println("******** FAILED to return an error");
		} catch (EndorseException | SubmitException | CommitStatusException e) {
			System.out.println("*** Successfully caught the error: ");
			e.printStackTrace(System.out);
			System.out.println("Transaction ID: " + e.getTransactionId());

			List<ErrorDetail> details = e.getDetails();
			if (!details.isEmpty()) {
				System.out.println("Error Details:");
				for (ErrorDetail detail : details) {
					System.out.println("- address: " + detail.getAddress() + ", mspId: " + detail.getMspId()
							+ ", message: " + detail.getMessage());
				}
			}
		} catch (CommitException e) {
			System.out.println("*** Successfully caught the error: " + e);
			e.printStackTrace(System.out);
			System.out.println("Transaction ID: " + e.getTransactionId());
			System.out.println("Status code: " + e.getCode());
		}
	}
	
}

위의 파일에서 cryptoPath 를 로컬에 받아둔 peerOrganizations의 경로로 수정한다. 그 다음 실행하면 바로 동작한다.

  • fabric-sample에 놀랍도록 친절하게 좋은 예시들이 많이 있다. erc20을 통한fungible token, erc721을 통한 non funcgible token(흔히 말하는 NFT), 새로운 organization 추가하기 등 ...
  • sh 스크립트들을 보면서 예제에 대해서 계속 공부를 해볼 생각이다.
profile
청포도루이보스민트티

1개의 댓글

comment-user-thumbnail
2023년 8월 30일

안녕하세요 패브릭을 공부하고 있는 신입개발자입니다 ㅎㅎ 글을 읽으며 따라해보던 중
"peerOrganizations를 로컬에 받아 둔다." 이 부분이 이해가 가지 않는데, 어떤식으로 받아두는 개념인지 혹시 알려주실 수 있을까요?

답글 달기