From f013705b2fbffe6852c868694a2423d9a1d0ca31 Mon Sep 17 00:00:00 2001 From: raventrov Date: Sat, 15 Dec 2018 14:14:22 +0800 Subject: [PATCH] A pure-Java implementation of the PlatON protocol --- .gitignore | 33 + README.md | 28 +- account/build.gradle | 5 + build.gradle | 64 + common/build.gradle | 26 + .../java/org/platon/common/AppenderName.java | 61 + .../java/org/platon/common/BasicPbCodec.java | 84 + .../org/platon/common/cache/DelayCache.java | 112 ++ .../org/platon/common/cache/DelayItem.java | 92 ++ .../common/config/ConfigProperties.java | 90 ++ .../org/platon/common/config/IgnoreLoad.java | 4 + .../org/platon/common/config/NodeConfig.java | 31 + .../org/platon/common/config/Validated.java | 11 + .../exceptions/DataDecodingException.java | 13 + .../exceptions/DataEncodingException.java | 13 + .../java/org/platon/common/utils/BIUtil.java | 114 ++ .../org/platon/common/utils/ByteArrayMap.java | 192 +++ .../org/platon/common/utils/ByteArraySet.java | 125 ++ .../platon/common/utils/ByteArrayWrapper.java | 53 + .../platon/common/utils/ByteComparator.java | 46 + .../org/platon/common/utils/ByteUtil.java | 711 +++++++++ .../org/platon/common/utils/FileUtil.java | 60 + .../org/platon/common/utils/LRUHashMap.java | 88 + .../java/org/platon/common/utils/Numeric.java | 214 +++ .../org/platon/common/utils/RandomUtils.java | 22 + .../org/platon/common/utils/SetAdapter.java | 90 ++ .../common/utils/SpringContextUtil.java | 33 + .../platon/common/utils/StringEnhance.java | 46 + .../org/platon/common/wrapper/DataWord.java | 405 +++++ .../org/platon/common/BasicPbCodecTest.java | 57 + .../org/platon/common/config/ConfigTest.java | 59 + .../common/utils/ByteComparatorTest.java | 27 + common/src/test/resources/test-platon.conf | 145 ++ config/platon.conf | 116 ++ consensus/build.gradle | 9 + core/build.gradle | 26 + core/src/main/java/org/platon/Start.java | 17 + .../main/java/org/platon/core/Account.java | 169 ++ .../java/org/platon/core/BlockIdentifier.java | 45 + .../java/org/platon/core/BlockSummary.java | 52 + .../main/java/org/platon/core/Blockchain.java | 64 + .../java/org/platon/core/BlockchainImpl.java | 962 +++++++++++ .../java/org/platon/core/Denomination.java | 44 + .../org/platon/core/EventDispatchWorker.java | 115 ++ .../java/org/platon/core/ExternalAccount.java | 46 + .../java/org/platon/core/ImportResult.java | 14 + .../java/org/platon/core/PendingStateIfc.java | 49 + .../org/platon/core/PendingStateImpl.java | 376 +++++ .../org/platon/core/PendingTransaction.java | 95 ++ .../main/java/org/platon/core/Permission.java | 80 + .../main/java/org/platon/core/Repository.java | 108 ++ .../core/TransactionExecutionSummary.java | 5 + .../java/org/platon/core/TransactionInfo.java | 46 + .../main/java/org/platon/core/TrieHash.java | 31 + .../src/main/java/org/platon/core/Worker.java | 147 ++ .../java/org/platon/core/block/Block.java | 129 ++ .../org/platon/core/block/BlockHeader.java | 433 +++++ .../java/org/platon/core/block/BlockInfo.java | 154 ++ .../java/org/platon/core/block/BlockPool.java | 144 ++ .../org/platon/core/block/GenesisBlock.java | 46 + .../org/platon/core/codec/DataWordCodec.java | 32 + .../core/codec/TransactionInfoCodec.java | 51 + .../core/codec/TransactionReceiptCodec.java | 76 + .../platon/core/config/BlockchainConfig.java | 62 + .../core/config/BlockchainNetConfig.java | 37 + .../org/platon/core/config/CommonConfig.java | 224 +++ .../org/platon/core/config/Constants.java | 30 + .../org/platon/core/config/CoreConfig.java | 204 +++ .../core/config/DBVersionProcessor.java | 118 ++ .../org/platon/core/config/DefaultConfig.java | 49 + .../org/platon/core/config/Initializer.java | 37 + .../org/platon/core/config/SystemConfig.java | 82 + .../platon/core/config/net/BaseNetConfig.java | 66 + .../platon/core/consensus/BaseConsensus.java | 97 ++ .../org/platon/core/consensus/BftWorker.java | 106 ++ .../core/consensus/ConsensusManager.java | 91 ++ .../org/platon/core/consensus/DposFace.java | 49 + .../platon/core/consensus/NoProofWorker.java | 70 + .../consensus/PrimaryOrderComparator.java | 29 + .../core/datasource/DataSourceArray.java | 60 + .../platon/core/datasource/JournalSource.java | 135 ++ .../core/datasource/ObjectDataSource.java | 24 + .../platon/core/datasource/Serializers.java | 102 ++ .../platon/core/datasource/SourceCodec.java | 66 + .../core/datasource/TransactionStore.java | 109 ++ .../platon/core/db/AbstractBlockstore.java | 18 + .../org/platon/core/db/BlockStoreIfc.java | 50 + .../org/platon/core/db/BlockStoreImpl.java | 467 ++++++ .../org/platon/core/db/ContractDetails.java | 65 + .../org/platon/core/db/DbFlushManager.java | 184 +++ .../java/org/platon/core/db/HeaderStore.java | 82 + .../org/platon/core/db/RepositoryImpl.java | 416 +++++ .../org/platon/core/db/RepositoryRoot.java | 134 ++ .../org/platon/core/db/RepositoryWrapper.java | 193 +++ .../java/org/platon/core/db/StateSource.java | 72 + .../platon/core/enums/AccountTypeEnum.java | 47 + .../java/org/platon/core/enums/ExtraInfo.java | 20 + .../core/exception/AlreadyKnownException.java | 10 + .../exception/AlreadySealedException.java | 10 + .../BadIrreversibleBlockException.java | 13 + .../exception/ErrorGenesisBlockException.java | 10 + .../core/exception/OperationException.java | 20 + .../core/exception/OutOfEnergonException.java | 10 + .../exception/OutOfPermissionException.java | 10 + .../core/exception/PlatonException.java | 28 + .../exception/UnknownParentException.java | 10 + .../org/platon/core/facade/CmdInterface.java | 89 ++ .../java/org/platon/core/facade/Platon.java | 26 + .../org/platon/core/facade/PlatonFactory.java | 38 + .../org/platon/core/facade/PlatonImpl.java | 112 ++ .../org/platon/core/facade/PlatonServer.java | 45 + .../platon/core/genesis/GenesisConfig.java | 79 + .../org/platon/core/genesis/GenesisJson.java | 117 ++ .../platon/core/genesis/GenesisLoader.java | 114 ++ .../platon/core/keystore/AccountHolder.java | 49 + .../platon/core/keystore/CipherParams.java | 22 + .../core/keystore/FileSystemKeystore.java | 141 ++ .../org/platon/core/keystore/KdfParams.java | 62 + .../org/platon/core/keystore/Keystore.java | 18 + .../platon/core/keystore/KeystoreCrypto.java | 75 + .../platon/core/keystore/KeystoreFormat.java | 200 +++ .../platon/core/keystore/KeystoreItem.java | 49 + .../listener/CompoplexPlatonListener.java | 129 ++ .../listener/PendingTransactionState.java | 42 + .../platon/core/listener/PlatonListener.java | 48 + .../core/listener/PlatonListenerAdapter.java | 52 + .../platon/core/listener/RunnableWrapper.java | 17 + .../platon/core/manager/InitialManager.java | 147 ++ .../platon/core/mine/BlockProduceManager.java | 348 ++++ .../org/platon/core/mine/CompositeFuture.java | 57 + .../java/org/platon/core/mine/MineTask.java | 37 + .../platon/core/mine/MinerAlgorithmIfc.java | 43 + .../org/platon/core/mine/MinerListener.java | 17 + .../org/platon/core/mine/MiningResult.java | 30 + .../platon/core/mine/NoMinerAlgorithm.java | 75 + .../java/org/platon/core/rpc/ProtoRpc.java | 62 + .../org/platon/core/rpc/ProtoRpcImpl.java | 538 +++++++ .../java/org/platon/core/rpc/Response.java | 77 + .../platon/core/rpc/model/AccountModel.java | 92 ++ .../core/rpc/servant/AtpGrpcServiceImpl.java | 326 ++++ .../rpc/wallet/FileSystemWalletStore.java | 63 + .../core/rpc/wallet/WalletAddressItem.java | 47 + .../org/platon/core/transaction/Bloom.java | 81 + .../core/transaction/ExecuteResult.java | 66 + .../org/platon/core/transaction/LogInfo.java | 159 ++ .../platon/core/transaction/Transaction.java | 722 +++++++++ .../transaction/TransactionComparator.java | 11 + .../core/transaction/TransactionPool.java | 143 ++ .../core/transaction/TransactionReceipt.java | 264 +++ .../core/transaction/util/ByteUtil.java | 729 +++++++++ .../core/transaction/util/HashUtil.java | 13 + .../org/platon/core/utils/CompactEncoder.java | 156 ++ .../org/platon/core/utils/DecodeResult.java | 44 + .../core/utils/TransactionSortedSet.java | 18 + .../java/org/platon/core/utils/Utils.java | 304 ++++ .../core/validator/TimelinessValidator.java | 165 ++ .../core/validator/model/ValidateBlock.java | 63 + .../java/org/platon/core/vm/CallCreate.java | 54 + .../java/org/platon/core/vm/EnergonCost.java | 299 ++++ .../java/org/platon/core/vm/MessageCall.java | 111 ++ .../main/java/org/platon/core/vm/OpCode.java | 798 +++++++++ .../platon/core/vm/PrecompiledContracts.java | 484 ++++++ core/src/main/java/org/platon/core/vm/VM.java | 1422 +++++++++++++++++ .../core/vm/program/InternalTransaction.java | 187 +++ .../org/platon/core/vm/program/Memory.java | 214 +++ .../org/platon/core/vm/program/Program.java | 1322 +++++++++++++++ .../core/vm/program/ProgramPrecompile.java | 99 ++ .../platon/core/vm/program/ProgramResult.java | 208 +++ .../org/platon/core/vm/program/Stack.java | 57 + .../org/platon/core/vm/program/Storage.java | 236 +++ .../core/vm/program/invoke/ProgramInvoke.java | 75 + .../program/invoke/ProgramInvokeFactory.java | 45 + .../invoke/ProgramInvokeFactoryImpl.java | 201 +++ .../vm/program/invoke/ProgramInvokeImpl.java | 342 ++++ .../program/invoke/ProgramInvokeMockImpl.java | 232 +++ .../listener/CompositeProgramListener.java | 86 + .../vm/program/listener/ProgramListener.java | 38 + .../listener/ProgramListenerAdaptor.java | 58 + .../listener/ProgramListenerAware.java | 23 + .../ProgramStorageChangeListener.java | 46 + .../java/org/platon/core/vm/trace/Op.java | 71 + .../org/platon/core/vm/trace/OpActions.java | 152 ++ .../platon/core/vm/trace/ProgramTrace.java | 118 ++ .../core/vm/trace/ProgramTraceListener.java | 79 + .../org/platon/core/vm/trace/Serializers.java | 91 ++ core/src/main/resources/config/genesis.json | 16 + .../java/org/platon/core/AccountTest.java | 48 + .../platon/core/block/BlockHeaderTest.java | 63 + .../org/platon/core/block/BlockPoolTest.java | 80 + .../java/org/platon/core/block/BlockTest.java | 99 ++ .../platon/core/config/CoreConfigTest.java | 34 + .../platon/core/config/DefaultConfigTest.java | 74 + .../org/platon/core/db/RepositoryTest.java | 928 +++++++++++ .../platon/core/facade/CmdInterfaceTest.java | 30 + .../platon/core/facade/PlatonFactoryTest.java | 29 + .../core/genesis/GenesisLoaderTest.java | 16 + .../platon/core/keystore/KeystoreTest.java | 137 ++ .../core/restucture/TransactionSendTest.java | 235 +++ .../org/platon/core/rpc/ProtoRpcTest.java | 278 ++++ .../test/java/org/platon/core/rpc/TxTest.java | 241 +++ .../java/org/platon/core/state/StateTest.java | 105 ++ .../core/transaction/TransactionPoolTest.java | 52 + .../transaction/TransactionReceiptTest.java | 99 ++ .../core/transaction/TransactionTest.java | 998 ++++++++++++ .../validator/TimelinessValidatorTest.java | 67 + core/src/test/resources/config/genesis.json | 16 + crypto/build.gradle | 18 + .../main/java/org/platon/crypto/Base58.java | 190 +++ .../java/org/platon/crypto/CheckUtil.java | 8 + .../crypto/ConcatKDFBytesGenerator.java | 162 ++ .../java/org/platon/crypto/ECIESCoder.java | 215 +++ .../java/org/platon/crypto/ECIESEngine.java | 463 ++++++ .../platon/crypto/ECIESPublicKeyEncoder.java | 32 + .../main/java/org/platon/crypto/ECKey.java | 875 ++++++++++ .../main/java/org/platon/crypto/HashUtil.java | 165 ++ .../platon/crypto/MGF1BytesGeneratorExt.java | 94 ++ .../java/org/platon/crypto/WalletUtil.java | 284 ++++ .../org/platon/crypto/config/Constants.java | 31 + .../org/platon/crypto/domain/WalletJson.java | 38 + .../java/org/platon/crypto/hash/Digest.java | 182 +++ .../org/platon/crypto/hash/DigestEngine.java | 279 ++++ .../org/platon/crypto/hash/Keccak256.java | 88 + .../org/platon/crypto/hash/Keccak512.java | 91 ++ .../org/platon/crypto/hash/KeccakCore.java | 596 +++++++ .../org/platon/crypto/jce/ECKeyAgreement.java | 57 + .../org/platon/crypto/jce/ECKeyFactory.java | 66 + .../platon/crypto/jce/ECKeyPairGenerator.java | 81 + .../platon/crypto/jce/ECSignatureFactory.java | 57 + .../crypto/jce/NewBouncyCastleProvider.java | 42 + .../java/org/platon/crypto/zksnark/BN128.java | 245 +++ .../org/platon/crypto/zksnark/BN128Fp.java | 87 + .../org/platon/crypto/zksnark/BN128Fp2.java | 91 ++ .../org/platon/crypto/zksnark/BN128G1.java | 63 + .../org/platon/crypto/zksnark/BN128G2.java | 88 + .../java/org/platon/crypto/zksnark/Field.java | 37 + .../java/org/platon/crypto/zksnark/Fp.java | 87 + .../java/org/platon/crypto/zksnark/Fp12.java | 358 +++++ .../java/org/platon/crypto/zksnark/Fp2.java | 172 ++ .../java/org/platon/crypto/zksnark/Fp6.java | 229 +++ .../platon/crypto/zksnark/PairingCheck.java | 300 ++++ .../org/platon/crypto/zksnark/Params.java | 66 + crypto/src/main/resources/logback.xml | 32 + .../java/org/platon/crypto/HashUtilTest.java | 27 + .../org/platon/crypto/WalletUtilTest.java | 117 ++ .../5f2ae1c60a3038956cf3355960cb211de78bab50 | 1 + crypto/src/test/resources/logback.xml | 32 + gradle.properties | 6 + gradlew | 172 ++ gradlew.bat | 84 + p2p/build.gradle | 30 + .../main/java/org/platon/p2p/EccDecoder.java | 56 + .../main/java/org/platon/p2p/EccEncoder.java | 66 + .../org/platon/p2p/ForwardMessageHook.java | 45 + .../main/java/org/platon/p2p/MessageHook.java | 44 + .../main/java/org/platon/p2p/NodeClient.java | 86 + .../platon/p2p/NodeClientChannelHandler.java | 36 + .../p2p/NodeClientChannelInitializer.java | 58 + .../main/java/org/platon/p2p/NodeContext.java | 50 + .../main/java/org/platon/p2p/NodeServer.java | 126 ++ .../platon/p2p/NodeServerChannelHandler.java | 30 + .../p2p/NodeServerChannelInitializer.java | 55 + .../org/platon/p2p/P2pChannelHandler.java | 143 ++ .../org/platon/p2p/attach/LinkController.java | 113 ++ .../org/platon/p2p/attach/LinkService.java | 104 ++ .../java/org/platon/p2p/common/Bytes.java | 100 ++ .../org/platon/p2p/common/CodecUtils.java | 13 + .../org/platon/p2p/common/HeaderHelper.java | 57 + .../platon/p2p/common/KadPluginConfig.java | 95 ++ .../org/platon/p2p/common/LmdbConfig.java | 53 + .../java/org/platon/p2p/common/NodeUtils.java | 118 ++ .../org/platon/p2p/common/PeerConfig.java | 46 + .../p2p/common/PlatonMessageHelper.java | 214 +++ .../org/platon/p2p/common/ProtoBufHelper.java | 50 + .../org/platon/p2p/common/ReDiRConfig.java | 66 + p2p/src/main/java/org/platon/p2p/db/DB.java | 32 + .../java/org/platon/p2p/db/DBException.java | 11 + .../java/org/platon/p2p/db/DBMemoryImp.java | 240 +++ .../main/java/org/platon/p2p/db/LmdbImp.java | 586 +++++++ .../p2p/handler/PlatonMessageHandler.java | 27 + .../handler/PlatonMessageHandlerContext.java | 126 ++ .../platon/p2p/handler/PlatonMessageType.java | 26 + .../platon/p2p/plugins/KadRoutingTable.java | 202 +++ .../platon/p2p/plugins/KadTopologyPlugin.java | 196 +++ .../java/org/platon/p2p/plugins/Plugin.java | 117 ++ .../org/platon/p2p/plugins/PluginFactory.java | 18 + .../org/platon/p2p/plugins/ResourceMap.java | 61 + .../p2p/plugins/RoutableIDComparator.java | 43 + .../org/platon/p2p/plugins/RoutingTable.java | 48 + .../platon/p2p/plugins/TopologyPlugin.java | 30 + .../p2p/plugins/TopologyPluginFactory.java | 13 + .../platon/p2p/plugins/kademlia/Contact.java | 101 ++ .../p2p/plugins/kademlia/KademliaBucket.java | 252 +++ .../p2p/plugins/kademlia/KademliaHelp.java | 94 ++ .../kademlia/KademliaRoutingTable.java | 232 +++ .../p2p/plugins/kademlia/KeyComparator.java | 33 + .../org/platon/p2p/pubsub/MessageIdCache.java | 55 + .../java/org/platon/p2p/pubsub/PubSub.java | 288 ++++ .../platon/p2p/pubsub/PubSubMessageHook.java | 20 + .../org/platon/p2p/pubsub/PubSubRouter.java | 606 +++++++ .../org/platon/p2p/pubsub/PubSubService.java | 29 + .../p2p/pubsub/PubSubSessionNotify.java | 46 + .../java/org/platon/p2p/pubsub/TimeCache.java | 55 + .../org/platon/p2p/redir/KeyAlgorithm.java | 51 + .../org/platon/p2p/redir/KeySelector.java | 220 +++ .../platon/p2p/redir/KeySelectorFactory.java | 31 + .../main/java/org/platon/p2p/redir/ReDiR.java | 307 ++++ .../org/platon/p2p/redir/ReDiRExecption.java | 10 + .../p2p/redir/ReDiRForwardMessageHook.java | 49 + .../platon/p2p/redir/ReDiRMessageHook.java | 78 + .../platon/p2p/redir/ServiceDiscovery.java | 46 + .../p2p/redir/ServiceDiscoveryManager.java | 285 ++++ .../redir/ServiceDiscoverySubCallback.java | 21 + .../p2p/redir/ServiceDiscoveryUtil.java | 54 + .../org/platon/p2p/redir/TurnService.java | 103 ++ .../org/platon/p2p/router/MessageRouter.java | 328 ++++ .../org/platon/p2p/router/RequestManager.java | 77 + .../platon/p2p/session/ChannelAttribute.java | 13 + .../p2p/session/CreateSessionHandler.java | 38 + .../java/org/platon/p2p/session/Session.java | 84 + .../platon/p2p/session/SessionManager.java | 357 +++++ .../org/platon/p2p/session/SessionNotify.java | 45 + .../platon/p2p/storage/StorageController.java | 214 +++ .../platon/p2p/storage/StorageService.java | 436 +++++ p2p/src/main/resources/config/node.conf | 99 ++ p2p/src/main/resources/config/node.properties | 121 ++ p2p/src/main/resources/logback.xml | 32 + .../java/org/platon/p2p/DelayCacheTest.java | 28 + .../test/java/org/platon/p2p/ECKeyTools.java | 24 + .../java/org/platon/p2p/NodeClientTest.java | 49 + .../java/org/platon/p2p/TestNodeServer1.java | 87 + .../java/org/platon/p2p/TestNodeServer2.java | 73 + .../java/org/platon/p2p/TestSeedServer.java | 11 + .../org/platon/p2p/netty/TimerClient.java | 100 ++ .../platon/p2p/netty/TimerClientHandler.java | 37 + .../org/platon/p2p/netty/TimerServer.java | 50 + .../platon/p2p/netty/TimerServerHandler.java | 26 + .../p2p/netty/platon/PlaonClientHandler.java | 54 + .../platon/p2p/netty/platon/PlatonClient.java | 126 ++ .../PlatonClientChannelInitializer.java | 58 + .../platon/p2p/netty/platon/PlatonServer.java | 89 ++ .../PlatonServerChannelInitializer.java | 50 + .../p2p/netty/platon/PlatonServerHandler.java | 44 + .../platon/p2p/plugins/RoutingTableMock.java | 136 ++ .../platon/p2p/plugins/TestResourceMap.java | 25 + .../platon/p2p/pubsub/PubSubClusterTest.java | 254 +++ .../p2p/pubsub/PubSubRequestCallback.java | 55 + .../platon/p2p/pubsub/PubSubRouterMock.java | 13 + .../platon/p2p/pubsub/PubSubRouterTest.java | 195 +++ .../p2p/pubsub/PubSubSubCallbackMock.java | 20 + .../org/platon/p2p/pubsub/PubsubMock.java | 32 + .../org/platon/p2p/pubsub/PubsubTest.java | 214 +++ .../org/platon/p2p/pubsub/TimeCacheTest.java | 36 + .../platon/p2p/redir/ReDiRClusterNode.java | 27 + .../redir/ReDiRClusterRequestCallback.java | 130 ++ .../platon/p2p/redir/ReDiRClusterTest.java | 158 ++ .../p2p/redir/ReDiRRequestCallback.java | 105 ++ .../p2p/redir/ServiceDiscoverManagerMock.java | 24 + .../p2p/redir/ServiceDiscoverManagerTest.java | 254 +++ .../platon/p2p/router/MessageRouterMock.java | 191 +++ .../org/platon/p2p/test/proto/AnyTest.java | 72 + p2p/src/test/resources/config/node1/node.conf | 54 + p2p/src/test/resources/config/node2/node.conf | 53 + p2p/src/test/resources/config/seed/node.conf | 48 + settings.gradle | 13 + slice/build.gradle | 63 + .../main/proto/org/platon/core/account.proto | 23 + .../src/main/proto/org/platon/core/base.proto | 20 + .../main/proto/org/platon/core/block.proto | 27 + .../proto/org/platon/core/blockheader.proto | 27 + .../main/proto/org/platon/core/dataword.proto | 26 + .../org/platon/core/indexBlockInfo.proto | 19 + .../proto/org/platon/core/message/errno.proto | 12 + .../org/platon/core/message/request.proto | 67 + .../org/platon/core/message/response.proto | 100 ++ .../org/platon/core/service/platon.gprc.proto | 70 + .../org/platon/core/transaction.v2.proto | 66 + .../org/platon/core/transactionInfo.proto | 22 + .../platon/core/transactionReceipt.v2.proto | 29 + .../proto/org/platon/p2p/attach/attach.proto | 28 + .../proto/org/platon/p2p/common/common.proto | 42 + .../org/platon/p2p/consensus/bftmessage.proto | 16 + .../proto/org/platon/p2p/platonmessage.proto | 31 + .../proto/org/platon/p2p/plugin/plugin.proto | 28 + .../proto/org/platon/p2p/pubsub/pubsub.proto | 83 + .../proto/org/platon/p2p/redir/redir.proto | 40 + .../org/platon/p2p/session/session.proto | 28 + .../org/platon/p2p/storage/storage.proto | 129 ++ .../slice/grpc/StreamObserverManager.java | 70 + storage/build.gradle | 21 + .../datasource/AbstractCachedSource.java | 79 + .../datasource/AbstractChainedSource.java | 41 + .../datasource/AsyncFlushable.java | 12 + .../datasource/AsyncWriteCache.java | 145 ++ .../datasource/BatchSource.java | 8 + .../datasource/BatchSourceWriter.java | 43 + .../datasource/CachedSource.java | 21 + .../datasource/DbSettings.java | 39 + .../datasource/DbSource.java | 24 + .../datasource/HashedKeySource.java | 4 + .../datasource/MemSizeEstimator.java | 12 + .../datasource/MultiCache.java | 51 + .../datasource/NoDeleteSource.java | 28 + .../datasource/NodeKeyCompositor.java | 50 + .../datasource/PrefixLookupSource.java | 33 + .../datasource/ReadCache.java | 129 ++ .../datasource/ReadWriteCache.java | 54 + .../datasource/SerializerIfc.java | 8 + .../org.platon.storage/datasource/Source.java | 18 + .../datasource/SourceChainBox.java | 40 + .../datasource/WriteCache.java | 270 ++++ .../datasource/XorDataSource.java | 37 + .../datasource/inmemory/HashMapDB.java | 137 ++ .../datasource/leveldb/LevelDBSource.java | 247 +++ .../datasource/rocksdb/RocksDBSource.java | 241 +++ .../enums/OverlimitStrategyEnum.java | 41 + .../exception/OverlimitException.java | 17 + .../org.platon.storage/trie/SecureTrie.java | 34 + .../java/org.platon.storage/trie/Trie.java | 12 + .../org.platon.storage/trie/TrieImpl.java | 1061 ++++++++++++ .../java/org.platon.storage/trie/TrieKey.java | 164 ++ .../org.platon.storage/trie/TrieProto.java | 961 +++++++++++ .../org.platon.storage/utils/AutoLock.java | 21 + .../java/org/platon/storage/StorageTest.java | 78 + .../storage/datasource/WriteCacheTest.java | 56 + .../datasource/leveldb/LevelDBSourceTest.java | 86 + .../datasource/rocksdb/RocksDBSourceTest.java | 83 + .../org/platon/storage/trie/TrieTest.java | 263 +++ .../src/test/resources/applicationContext.xml | 31 + .../test/resources/config/system.properties | 2 + storage/src/test/resources/logback.xml | 32 + tests/build.gradle | 9 + vm/build.gradle | 10 + 432 files changed, 51545 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 account/build.gradle create mode 100644 build.gradle create mode 100644 common/build.gradle create mode 100644 common/src/main/java/org/platon/common/AppenderName.java create mode 100644 common/src/main/java/org/platon/common/BasicPbCodec.java create mode 100644 common/src/main/java/org/platon/common/cache/DelayCache.java create mode 100644 common/src/main/java/org/platon/common/cache/DelayItem.java create mode 100644 common/src/main/java/org/platon/common/config/ConfigProperties.java create mode 100644 common/src/main/java/org/platon/common/config/IgnoreLoad.java create mode 100644 common/src/main/java/org/platon/common/config/NodeConfig.java create mode 100644 common/src/main/java/org/platon/common/config/Validated.java create mode 100644 common/src/main/java/org/platon/common/exceptions/DataDecodingException.java create mode 100644 common/src/main/java/org/platon/common/exceptions/DataEncodingException.java create mode 100644 common/src/main/java/org/platon/common/utils/BIUtil.java create mode 100644 common/src/main/java/org/platon/common/utils/ByteArrayMap.java create mode 100644 common/src/main/java/org/platon/common/utils/ByteArraySet.java create mode 100644 common/src/main/java/org/platon/common/utils/ByteArrayWrapper.java create mode 100644 common/src/main/java/org/platon/common/utils/ByteComparator.java create mode 100644 common/src/main/java/org/platon/common/utils/ByteUtil.java create mode 100644 common/src/main/java/org/platon/common/utils/FileUtil.java create mode 100644 common/src/main/java/org/platon/common/utils/LRUHashMap.java create mode 100644 common/src/main/java/org/platon/common/utils/Numeric.java create mode 100644 common/src/main/java/org/platon/common/utils/RandomUtils.java create mode 100644 common/src/main/java/org/platon/common/utils/SetAdapter.java create mode 100644 common/src/main/java/org/platon/common/utils/SpringContextUtil.java create mode 100644 common/src/main/java/org/platon/common/utils/StringEnhance.java create mode 100644 common/src/main/java/org/platon/common/wrapper/DataWord.java create mode 100644 common/src/test/java/org/platon/common/BasicPbCodecTest.java create mode 100644 common/src/test/java/org/platon/common/config/ConfigTest.java create mode 100644 common/src/test/java/org/platon/common/utils/ByteComparatorTest.java create mode 100644 common/src/test/resources/test-platon.conf create mode 100644 config/platon.conf create mode 100644 consensus/build.gradle create mode 100644 core/build.gradle create mode 100644 core/src/main/java/org/platon/Start.java create mode 100644 core/src/main/java/org/platon/core/Account.java create mode 100644 core/src/main/java/org/platon/core/BlockIdentifier.java create mode 100644 core/src/main/java/org/platon/core/BlockSummary.java create mode 100644 core/src/main/java/org/platon/core/Blockchain.java create mode 100644 core/src/main/java/org/platon/core/BlockchainImpl.java create mode 100644 core/src/main/java/org/platon/core/Denomination.java create mode 100644 core/src/main/java/org/platon/core/EventDispatchWorker.java create mode 100644 core/src/main/java/org/platon/core/ExternalAccount.java create mode 100644 core/src/main/java/org/platon/core/ImportResult.java create mode 100644 core/src/main/java/org/platon/core/PendingStateIfc.java create mode 100644 core/src/main/java/org/platon/core/PendingStateImpl.java create mode 100644 core/src/main/java/org/platon/core/PendingTransaction.java create mode 100644 core/src/main/java/org/platon/core/Permission.java create mode 100644 core/src/main/java/org/platon/core/Repository.java create mode 100644 core/src/main/java/org/platon/core/TransactionExecutionSummary.java create mode 100644 core/src/main/java/org/platon/core/TransactionInfo.java create mode 100644 core/src/main/java/org/platon/core/TrieHash.java create mode 100644 core/src/main/java/org/platon/core/Worker.java create mode 100644 core/src/main/java/org/platon/core/block/Block.java create mode 100644 core/src/main/java/org/platon/core/block/BlockHeader.java create mode 100644 core/src/main/java/org/platon/core/block/BlockInfo.java create mode 100644 core/src/main/java/org/platon/core/block/BlockPool.java create mode 100644 core/src/main/java/org/platon/core/block/GenesisBlock.java create mode 100644 core/src/main/java/org/platon/core/codec/DataWordCodec.java create mode 100644 core/src/main/java/org/platon/core/codec/TransactionInfoCodec.java create mode 100644 core/src/main/java/org/platon/core/codec/TransactionReceiptCodec.java create mode 100644 core/src/main/java/org/platon/core/config/BlockchainConfig.java create mode 100644 core/src/main/java/org/platon/core/config/BlockchainNetConfig.java create mode 100644 core/src/main/java/org/platon/core/config/CommonConfig.java create mode 100644 core/src/main/java/org/platon/core/config/Constants.java create mode 100644 core/src/main/java/org/platon/core/config/CoreConfig.java create mode 100644 core/src/main/java/org/platon/core/config/DBVersionProcessor.java create mode 100644 core/src/main/java/org/platon/core/config/DefaultConfig.java create mode 100644 core/src/main/java/org/platon/core/config/Initializer.java create mode 100644 core/src/main/java/org/platon/core/config/SystemConfig.java create mode 100644 core/src/main/java/org/platon/core/config/net/BaseNetConfig.java create mode 100644 core/src/main/java/org/platon/core/consensus/BaseConsensus.java create mode 100644 core/src/main/java/org/platon/core/consensus/BftWorker.java create mode 100644 core/src/main/java/org/platon/core/consensus/ConsensusManager.java create mode 100644 core/src/main/java/org/platon/core/consensus/DposFace.java create mode 100644 core/src/main/java/org/platon/core/consensus/NoProofWorker.java create mode 100644 core/src/main/java/org/platon/core/consensus/PrimaryOrderComparator.java create mode 100644 core/src/main/java/org/platon/core/datasource/DataSourceArray.java create mode 100644 core/src/main/java/org/platon/core/datasource/JournalSource.java create mode 100644 core/src/main/java/org/platon/core/datasource/ObjectDataSource.java create mode 100644 core/src/main/java/org/platon/core/datasource/Serializers.java create mode 100644 core/src/main/java/org/platon/core/datasource/SourceCodec.java create mode 100644 core/src/main/java/org/platon/core/datasource/TransactionStore.java create mode 100644 core/src/main/java/org/platon/core/db/AbstractBlockstore.java create mode 100644 core/src/main/java/org/platon/core/db/BlockStoreIfc.java create mode 100644 core/src/main/java/org/platon/core/db/BlockStoreImpl.java create mode 100644 core/src/main/java/org/platon/core/db/ContractDetails.java create mode 100644 core/src/main/java/org/platon/core/db/DbFlushManager.java create mode 100644 core/src/main/java/org/platon/core/db/HeaderStore.java create mode 100644 core/src/main/java/org/platon/core/db/RepositoryImpl.java create mode 100644 core/src/main/java/org/platon/core/db/RepositoryRoot.java create mode 100644 core/src/main/java/org/platon/core/db/RepositoryWrapper.java create mode 100644 core/src/main/java/org/platon/core/db/StateSource.java create mode 100644 core/src/main/java/org/platon/core/enums/AccountTypeEnum.java create mode 100644 core/src/main/java/org/platon/core/enums/ExtraInfo.java create mode 100644 core/src/main/java/org/platon/core/exception/AlreadyKnownException.java create mode 100644 core/src/main/java/org/platon/core/exception/AlreadySealedException.java create mode 100644 core/src/main/java/org/platon/core/exception/BadIrreversibleBlockException.java create mode 100644 core/src/main/java/org/platon/core/exception/ErrorGenesisBlockException.java create mode 100644 core/src/main/java/org/platon/core/exception/OperationException.java create mode 100644 core/src/main/java/org/platon/core/exception/OutOfEnergonException.java create mode 100644 core/src/main/java/org/platon/core/exception/OutOfPermissionException.java create mode 100644 core/src/main/java/org/platon/core/exception/PlatonException.java create mode 100644 core/src/main/java/org/platon/core/exception/UnknownParentException.java create mode 100644 core/src/main/java/org/platon/core/facade/CmdInterface.java create mode 100644 core/src/main/java/org/platon/core/facade/Platon.java create mode 100644 core/src/main/java/org/platon/core/facade/PlatonFactory.java create mode 100644 core/src/main/java/org/platon/core/facade/PlatonImpl.java create mode 100644 core/src/main/java/org/platon/core/facade/PlatonServer.java create mode 100644 core/src/main/java/org/platon/core/genesis/GenesisConfig.java create mode 100644 core/src/main/java/org/platon/core/genesis/GenesisJson.java create mode 100644 core/src/main/java/org/platon/core/genesis/GenesisLoader.java create mode 100644 core/src/main/java/org/platon/core/keystore/AccountHolder.java create mode 100644 core/src/main/java/org/platon/core/keystore/CipherParams.java create mode 100644 core/src/main/java/org/platon/core/keystore/FileSystemKeystore.java create mode 100644 core/src/main/java/org/platon/core/keystore/KdfParams.java create mode 100644 core/src/main/java/org/platon/core/keystore/Keystore.java create mode 100644 core/src/main/java/org/platon/core/keystore/KeystoreCrypto.java create mode 100644 core/src/main/java/org/platon/core/keystore/KeystoreFormat.java create mode 100644 core/src/main/java/org/platon/core/keystore/KeystoreItem.java create mode 100644 core/src/main/java/org/platon/core/listener/CompoplexPlatonListener.java create mode 100644 core/src/main/java/org/platon/core/listener/PendingTransactionState.java create mode 100644 core/src/main/java/org/platon/core/listener/PlatonListener.java create mode 100644 core/src/main/java/org/platon/core/listener/PlatonListenerAdapter.java create mode 100644 core/src/main/java/org/platon/core/listener/RunnableWrapper.java create mode 100644 core/src/main/java/org/platon/core/manager/InitialManager.java create mode 100644 core/src/main/java/org/platon/core/mine/BlockProduceManager.java create mode 100644 core/src/main/java/org/platon/core/mine/CompositeFuture.java create mode 100644 core/src/main/java/org/platon/core/mine/MineTask.java create mode 100644 core/src/main/java/org/platon/core/mine/MinerAlgorithmIfc.java create mode 100644 core/src/main/java/org/platon/core/mine/MinerListener.java create mode 100644 core/src/main/java/org/platon/core/mine/MiningResult.java create mode 100644 core/src/main/java/org/platon/core/mine/NoMinerAlgorithm.java create mode 100644 core/src/main/java/org/platon/core/rpc/ProtoRpc.java create mode 100644 core/src/main/java/org/platon/core/rpc/ProtoRpcImpl.java create mode 100644 core/src/main/java/org/platon/core/rpc/Response.java create mode 100644 core/src/main/java/org/platon/core/rpc/model/AccountModel.java create mode 100644 core/src/main/java/org/platon/core/rpc/servant/AtpGrpcServiceImpl.java create mode 100644 core/src/main/java/org/platon/core/rpc/wallet/FileSystemWalletStore.java create mode 100644 core/src/main/java/org/platon/core/rpc/wallet/WalletAddressItem.java create mode 100644 core/src/main/java/org/platon/core/transaction/Bloom.java create mode 100644 core/src/main/java/org/platon/core/transaction/ExecuteResult.java create mode 100644 core/src/main/java/org/platon/core/transaction/LogInfo.java create mode 100644 core/src/main/java/org/platon/core/transaction/Transaction.java create mode 100644 core/src/main/java/org/platon/core/transaction/TransactionComparator.java create mode 100644 core/src/main/java/org/platon/core/transaction/TransactionPool.java create mode 100644 core/src/main/java/org/platon/core/transaction/TransactionReceipt.java create mode 100644 core/src/main/java/org/platon/core/transaction/util/ByteUtil.java create mode 100644 core/src/main/java/org/platon/core/transaction/util/HashUtil.java create mode 100644 core/src/main/java/org/platon/core/utils/CompactEncoder.java create mode 100644 core/src/main/java/org/platon/core/utils/DecodeResult.java create mode 100644 core/src/main/java/org/platon/core/utils/TransactionSortedSet.java create mode 100644 core/src/main/java/org/platon/core/utils/Utils.java create mode 100644 core/src/main/java/org/platon/core/validator/TimelinessValidator.java create mode 100644 core/src/main/java/org/platon/core/validator/model/ValidateBlock.java create mode 100644 core/src/main/java/org/platon/core/vm/CallCreate.java create mode 100644 core/src/main/java/org/platon/core/vm/EnergonCost.java create mode 100644 core/src/main/java/org/platon/core/vm/MessageCall.java create mode 100644 core/src/main/java/org/platon/core/vm/OpCode.java create mode 100644 core/src/main/java/org/platon/core/vm/PrecompiledContracts.java create mode 100644 core/src/main/java/org/platon/core/vm/VM.java create mode 100644 core/src/main/java/org/platon/core/vm/program/InternalTransaction.java create mode 100644 core/src/main/java/org/platon/core/vm/program/Memory.java create mode 100644 core/src/main/java/org/platon/core/vm/program/Program.java create mode 100644 core/src/main/java/org/platon/core/vm/program/ProgramPrecompile.java create mode 100644 core/src/main/java/org/platon/core/vm/program/ProgramResult.java create mode 100644 core/src/main/java/org/platon/core/vm/program/Stack.java create mode 100644 core/src/main/java/org/platon/core/vm/program/Storage.java create mode 100644 core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvoke.java create mode 100644 core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactory.java create mode 100644 core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactoryImpl.java create mode 100644 core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeImpl.java create mode 100644 core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeMockImpl.java create mode 100644 core/src/main/java/org/platon/core/vm/program/listener/CompositeProgramListener.java create mode 100644 core/src/main/java/org/platon/core/vm/program/listener/ProgramListener.java create mode 100644 core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAdaptor.java create mode 100644 core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAware.java create mode 100644 core/src/main/java/org/platon/core/vm/program/listener/ProgramStorageChangeListener.java create mode 100644 core/src/main/java/org/platon/core/vm/trace/Op.java create mode 100644 core/src/main/java/org/platon/core/vm/trace/OpActions.java create mode 100644 core/src/main/java/org/platon/core/vm/trace/ProgramTrace.java create mode 100644 core/src/main/java/org/platon/core/vm/trace/ProgramTraceListener.java create mode 100644 core/src/main/java/org/platon/core/vm/trace/Serializers.java create mode 100644 core/src/main/resources/config/genesis.json create mode 100644 core/src/test/java/org/platon/core/AccountTest.java create mode 100644 core/src/test/java/org/platon/core/block/BlockHeaderTest.java create mode 100644 core/src/test/java/org/platon/core/block/BlockPoolTest.java create mode 100644 core/src/test/java/org/platon/core/block/BlockTest.java create mode 100644 core/src/test/java/org/platon/core/config/CoreConfigTest.java create mode 100644 core/src/test/java/org/platon/core/config/DefaultConfigTest.java create mode 100644 core/src/test/java/org/platon/core/db/RepositoryTest.java create mode 100644 core/src/test/java/org/platon/core/facade/CmdInterfaceTest.java create mode 100644 core/src/test/java/org/platon/core/facade/PlatonFactoryTest.java create mode 100644 core/src/test/java/org/platon/core/genesis/GenesisLoaderTest.java create mode 100644 core/src/test/java/org/platon/core/keystore/KeystoreTest.java create mode 100644 core/src/test/java/org/platon/core/restucture/TransactionSendTest.java create mode 100644 core/src/test/java/org/platon/core/rpc/ProtoRpcTest.java create mode 100644 core/src/test/java/org/platon/core/rpc/TxTest.java create mode 100644 core/src/test/java/org/platon/core/state/StateTest.java create mode 100644 core/src/test/java/org/platon/core/transaction/TransactionPoolTest.java create mode 100644 core/src/test/java/org/platon/core/transaction/TransactionReceiptTest.java create mode 100644 core/src/test/java/org/platon/core/transaction/TransactionTest.java create mode 100644 core/src/test/java/org/platon/core/validator/TimelinessValidatorTest.java create mode 100644 core/src/test/resources/config/genesis.json create mode 100644 crypto/build.gradle create mode 100644 crypto/src/main/java/org/platon/crypto/Base58.java create mode 100644 crypto/src/main/java/org/platon/crypto/CheckUtil.java create mode 100644 crypto/src/main/java/org/platon/crypto/ConcatKDFBytesGenerator.java create mode 100644 crypto/src/main/java/org/platon/crypto/ECIESCoder.java create mode 100644 crypto/src/main/java/org/platon/crypto/ECIESEngine.java create mode 100644 crypto/src/main/java/org/platon/crypto/ECIESPublicKeyEncoder.java create mode 100644 crypto/src/main/java/org/platon/crypto/ECKey.java create mode 100644 crypto/src/main/java/org/platon/crypto/HashUtil.java create mode 100644 crypto/src/main/java/org/platon/crypto/MGF1BytesGeneratorExt.java create mode 100644 crypto/src/main/java/org/platon/crypto/WalletUtil.java create mode 100644 crypto/src/main/java/org/platon/crypto/config/Constants.java create mode 100644 crypto/src/main/java/org/platon/crypto/domain/WalletJson.java create mode 100644 crypto/src/main/java/org/platon/crypto/hash/Digest.java create mode 100644 crypto/src/main/java/org/platon/crypto/hash/DigestEngine.java create mode 100644 crypto/src/main/java/org/platon/crypto/hash/Keccak256.java create mode 100644 crypto/src/main/java/org/platon/crypto/hash/Keccak512.java create mode 100644 crypto/src/main/java/org/platon/crypto/hash/KeccakCore.java create mode 100644 crypto/src/main/java/org/platon/crypto/jce/ECKeyAgreement.java create mode 100644 crypto/src/main/java/org/platon/crypto/jce/ECKeyFactory.java create mode 100644 crypto/src/main/java/org/platon/crypto/jce/ECKeyPairGenerator.java create mode 100644 crypto/src/main/java/org/platon/crypto/jce/ECSignatureFactory.java create mode 100644 crypto/src/main/java/org/platon/crypto/jce/NewBouncyCastleProvider.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/BN128.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp2.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/BN128G1.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/BN128G2.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Field.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Fp.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Fp12.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Fp2.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Fp6.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/PairingCheck.java create mode 100644 crypto/src/main/java/org/platon/crypto/zksnark/Params.java create mode 100644 crypto/src/main/resources/logback.xml create mode 100644 crypto/src/test/java/org/platon/crypto/HashUtilTest.java create mode 100644 crypto/src/test/java/org/platon/crypto/WalletUtilTest.java create mode 100644 crypto/src/test/resources/keys/5f2ae1c60a3038956cf3355960cb211de78bab50 create mode 100644 crypto/src/test/resources/logback.xml create mode 100644 gradle.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 p2p/build.gradle create mode 100644 p2p/src/main/java/org/platon/p2p/EccDecoder.java create mode 100644 p2p/src/main/java/org/platon/p2p/EccEncoder.java create mode 100644 p2p/src/main/java/org/platon/p2p/ForwardMessageHook.java create mode 100644 p2p/src/main/java/org/platon/p2p/MessageHook.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeClient.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeClientChannelHandler.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeClientChannelInitializer.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeContext.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeServer.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeServerChannelHandler.java create mode 100644 p2p/src/main/java/org/platon/p2p/NodeServerChannelInitializer.java create mode 100644 p2p/src/main/java/org/platon/p2p/P2pChannelHandler.java create mode 100644 p2p/src/main/java/org/platon/p2p/attach/LinkController.java create mode 100644 p2p/src/main/java/org/platon/p2p/attach/LinkService.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/Bytes.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/CodecUtils.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/HeaderHelper.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/KadPluginConfig.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/LmdbConfig.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/NodeUtils.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/PeerConfig.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/PlatonMessageHelper.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/ProtoBufHelper.java create mode 100644 p2p/src/main/java/org/platon/p2p/common/ReDiRConfig.java create mode 100644 p2p/src/main/java/org/platon/p2p/db/DB.java create mode 100644 p2p/src/main/java/org/platon/p2p/db/DBException.java create mode 100644 p2p/src/main/java/org/platon/p2p/db/DBMemoryImp.java create mode 100644 p2p/src/main/java/org/platon/p2p/db/LmdbImp.java create mode 100644 p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandler.java create mode 100644 p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandlerContext.java create mode 100644 p2p/src/main/java/org/platon/p2p/handler/PlatonMessageType.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/KadRoutingTable.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/KadTopologyPlugin.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/Plugin.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/PluginFactory.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/ResourceMap.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/RoutableIDComparator.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/RoutingTable.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/TopologyPlugin.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/TopologyPluginFactory.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/kademlia/Contact.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaBucket.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaHelp.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaRoutingTable.java create mode 100644 p2p/src/main/java/org/platon/p2p/plugins/kademlia/KeyComparator.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/MessageIdCache.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/PubSub.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/PubSubMessageHook.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/PubSubRouter.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/PubSubService.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/PubSubSessionNotify.java create mode 100644 p2p/src/main/java/org/platon/p2p/pubsub/TimeCache.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/KeyAlgorithm.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/KeySelector.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/KeySelectorFactory.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ReDiR.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ReDiRExecption.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ReDiRForwardMessageHook.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ReDiRMessageHook.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ServiceDiscovery.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryManager.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoverySubCallback.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryUtil.java create mode 100644 p2p/src/main/java/org/platon/p2p/redir/TurnService.java create mode 100644 p2p/src/main/java/org/platon/p2p/router/MessageRouter.java create mode 100644 p2p/src/main/java/org/platon/p2p/router/RequestManager.java create mode 100644 p2p/src/main/java/org/platon/p2p/session/ChannelAttribute.java create mode 100644 p2p/src/main/java/org/platon/p2p/session/CreateSessionHandler.java create mode 100644 p2p/src/main/java/org/platon/p2p/session/Session.java create mode 100644 p2p/src/main/java/org/platon/p2p/session/SessionManager.java create mode 100644 p2p/src/main/java/org/platon/p2p/session/SessionNotify.java create mode 100644 p2p/src/main/java/org/platon/p2p/storage/StorageController.java create mode 100644 p2p/src/main/java/org/platon/p2p/storage/StorageService.java create mode 100644 p2p/src/main/resources/config/node.conf create mode 100644 p2p/src/main/resources/config/node.properties create mode 100644 p2p/src/main/resources/logback.xml create mode 100644 p2p/src/test/java/org/platon/p2p/DelayCacheTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/ECKeyTools.java create mode 100644 p2p/src/test/java/org/platon/p2p/NodeClientTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/TestNodeServer1.java create mode 100644 p2p/src/test/java/org/platon/p2p/TestNodeServer2.java create mode 100644 p2p/src/test/java/org/platon/p2p/TestSeedServer.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/TimerClient.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/TimerClientHandler.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/TimerServer.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/TimerServerHandler.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlaonClientHandler.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClient.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClientChannelInitializer.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServer.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerChannelInitializer.java create mode 100644 p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerHandler.java create mode 100644 p2p/src/test/java/org/platon/p2p/plugins/RoutingTableMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/plugins/TestResourceMap.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubSubClusterTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubSubRequestCallback.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubSubSubCallbackMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubsubMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/PubsubTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/pubsub/TimeCacheTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterNode.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterRequestCallback.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ReDiRRequestCallback.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerTest.java create mode 100644 p2p/src/test/java/org/platon/p2p/router/MessageRouterMock.java create mode 100644 p2p/src/test/java/org/platon/p2p/test/proto/AnyTest.java create mode 100644 p2p/src/test/resources/config/node1/node.conf create mode 100644 p2p/src/test/resources/config/node2/node.conf create mode 100644 p2p/src/test/resources/config/seed/node.conf create mode 100644 settings.gradle create mode 100644 slice/build.gradle create mode 100644 slice/src/main/proto/org/platon/core/account.proto create mode 100644 slice/src/main/proto/org/platon/core/base.proto create mode 100644 slice/src/main/proto/org/platon/core/block.proto create mode 100644 slice/src/main/proto/org/platon/core/blockheader.proto create mode 100644 slice/src/main/proto/org/platon/core/dataword.proto create mode 100644 slice/src/main/proto/org/platon/core/indexBlockInfo.proto create mode 100644 slice/src/main/proto/org/platon/core/message/errno.proto create mode 100644 slice/src/main/proto/org/platon/core/message/request.proto create mode 100644 slice/src/main/proto/org/platon/core/message/response.proto create mode 100644 slice/src/main/proto/org/platon/core/service/platon.gprc.proto create mode 100644 slice/src/main/proto/org/platon/core/transaction.v2.proto create mode 100644 slice/src/main/proto/org/platon/core/transactionInfo.proto create mode 100644 slice/src/main/proto/org/platon/core/transactionReceipt.v2.proto create mode 100644 slice/src/main/proto/org/platon/p2p/attach/attach.proto create mode 100644 slice/src/main/proto/org/platon/p2p/common/common.proto create mode 100644 slice/src/main/proto/org/platon/p2p/consensus/bftmessage.proto create mode 100644 slice/src/main/proto/org/platon/p2p/platonmessage.proto create mode 100644 slice/src/main/proto/org/platon/p2p/plugin/plugin.proto create mode 100644 slice/src/main/proto/org/platon/p2p/pubsub/pubsub.proto create mode 100644 slice/src/main/proto/org/platon/p2p/redir/redir.proto create mode 100644 slice/src/main/proto/org/platon/p2p/session/session.proto create mode 100644 slice/src/main/proto/org/platon/p2p/storage/storage.proto create mode 100644 slice/src/test/java/org/platon/slice/grpc/StreamObserverManager.java create mode 100644 storage/build.gradle create mode 100644 storage/src/main/java/org.platon.storage/datasource/AbstractCachedSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/AbstractChainedSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/AsyncFlushable.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/AsyncWriteCache.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/BatchSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/BatchSourceWriter.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/CachedSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/DbSettings.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/DbSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/HashedKeySource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/MemSizeEstimator.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/MultiCache.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/NoDeleteSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/NodeKeyCompositor.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/PrefixLookupSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/ReadCache.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/ReadWriteCache.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/SerializerIfc.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/Source.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/SourceChainBox.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/WriteCache.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/XorDataSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/inmemory/HashMapDB.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/leveldb/LevelDBSource.java create mode 100644 storage/src/main/java/org.platon.storage/datasource/rocksdb/RocksDBSource.java create mode 100644 storage/src/main/java/org.platon.storage/enums/OverlimitStrategyEnum.java create mode 100644 storage/src/main/java/org.platon.storage/exception/OverlimitException.java create mode 100644 storage/src/main/java/org.platon.storage/trie/SecureTrie.java create mode 100644 storage/src/main/java/org.platon.storage/trie/Trie.java create mode 100644 storage/src/main/java/org.platon.storage/trie/TrieImpl.java create mode 100644 storage/src/main/java/org.platon.storage/trie/TrieKey.java create mode 100644 storage/src/main/java/org.platon.storage/trie/TrieProto.java create mode 100644 storage/src/main/java/org.platon.storage/utils/AutoLock.java create mode 100644 storage/src/test/java/org/platon/storage/StorageTest.java create mode 100644 storage/src/test/java/org/platon/storage/datasource/WriteCacheTest.java create mode 100644 storage/src/test/java/org/platon/storage/datasource/leveldb/LevelDBSourceTest.java create mode 100644 storage/src/test/java/org/platon/storage/datasource/rocksdb/RocksDBSourceTest.java create mode 100644 storage/src/test/java/org/platon/storage/trie/TrieTest.java create mode 100644 storage/src/test/resources/applicationContext.xml create mode 100644 storage/src/test/resources/config/system.properties create mode 100644 storage/src/test/resources/logback.xml create mode 100644 tests/build.gradle create mode 100644 vm/build.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcf4174 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +.gradle +gradle +.idea +*.impl +*.iml +*.log +*.bak +*.class +logs/* +build/* +account/build/ +common/build/ +consensus/build/ +core/build/ +core/out/ +crypto/build/ +crypto/out/ +p2p/build/ +slice/build/ +storage/build/ +storage/out/ +out/* +slice/src/main/java/ +slice/src/main/grpc/ +core/db-01/ +core/database/ +slice/src/main/java/ +database/ +testdb/ + +out +core/config +core/blockchain diff --git a/README.md b/README.md index 904f63a..9a6ed3e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ -# PlatON-Java -Java implementation of the PlatON protocol +# Welcome to PlatON-Java + +# About + +PlatON-Java is a pure-Java implementation of the PlatON protocol. + +# Running PlatON-Java + +##### Importing project to IntelliJ IDEA: + +``` +> git clone https://github.com/PlatONnetwork/PlatON-Java.git +> cd PlatON-Java +> ./gradlew.bat build +``` + + IDEA: +* File -> New -> Project from existing sources… +* Select PlatON-Java/build.gradle +* Dialog “Import Project from gradle”: press “OK” +* After building run `org.platon.Start` . + + +# License +PlatON-Java is released under the [LGPL-V3 license](LICENSE). + diff --git a/account/build.gradle b/account/build.gradle new file mode 100644 index 0000000..9306e92 --- /dev/null +++ b/account/build.gradle @@ -0,0 +1,5 @@ +sourceCompatibility = 1.8 + +dependencies { + +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e131f34 --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +subprojects { + + group 'org.platon' + version '0.1.0-SNAPSHOT' + + /** load plugin */ + apply plugin: 'java' + apply plugin: 'maven' + + compileJava.options.encoding = 'UTF-8' + compileJava.options.compilerArgs << '-XDignore.symbol.file' + compileTestJava.options.encoding = 'UTF-8' + + ext { + iceVersion = '3.7.1' + springVersion = '4.2.0.RELEASE' + } + + /** define resp */ + repositories { + maven { + url "http://maven.aliyun.com/nexus/content/groups/public/" + } + } + + /** define common dependency */ + dependencies { + compile "ch.qos.logback:logback-classic:1.1.7" + compile "ch.qos.logback:logback-core:1.1.7" + compile "org.slf4j:slf4j-api:1.7.7" + compile "org.slf4j:slf4j-api:1.7.7" + compile "org.slf4j:log4j-over-slf4j:1.7.7" + compile "org.slf4j:jul-to-slf4j:1.7.7" + compile "org.slf4j:jcl-over-slf4j:1.7.7" + + // spring define + compile "org.springframework:spring-context:${springVersion}" + compile "org.springframework:spring-orm:${springVersion}" + compile "org.springframework:spring-tx:${springVersion}" + + // config + compile "com.typesafe:config:1.2.1" + compile "com.googlecode.concurrent-locks:concurrent-locks:1.0.0" + + testCompile "org.springframework:spring-test:${springVersion}" + testCompile "org.mockito:mockito-core:2.19.1" + testCompile group: 'junit', name: 'junit', version: '4.11' + testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.0-beta.5' + testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.0-beta.5' + } + + task "create-dir" << { + sourceSets*.java.srcDirs*.each { + it.mkdirs() + } + sourceSets*.resources.srcDirs*.each { + it.mkdirs() + } + } +} + +task wrapper(type: Wrapper) { + gradleVersion = "4.7" +} \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..cb57ef9 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,26 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + + compile project(':slice') + compile "com.google.guava:guava:24.1-jre" + + compile "com.madgag.spongycastle:core:1.58.0.0" // for SHA3 and SECP256K1 + compile "com.madgag.spongycastle:prov:1.58.0.0" // for SHA3 and SECP256K1 + compile "com.fasterxml.jackson.core:jackson-databind:2.5.1" + compile "com.fasterxml.jackson.core:jackson-annotations:2.5.0" + compile "org.apache.commons:commons-collections4:4.0" + + compile "com.cedarsoftware:java-util:1.8.0" + + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +task createJavaProject << { + sourceSets*.java.srcDirs*.each{ it.mkdirs() } + sourceSets*.resources.srcDirs*.each{ it.mkdirs()} +} diff --git a/common/src/main/java/org/platon/common/AppenderName.java b/common/src/main/java/org/platon/common/AppenderName.java new file mode 100644 index 0000000..0d1cc18 --- /dev/null +++ b/common/src/main/java/org/platon/common/AppenderName.java @@ -0,0 +1,61 @@ +package org.platon.common; + +import org.slf4j.LoggerFactory; + +public interface AppenderName { + + final static String APPENDER_CONSENSUS = "consensus"; + + final static String APPENDER_KEY_STORE = "keystore"; + + final static String APPENDER_PLATIN = "plain"; + + final static String APPENDER_RPC = "grpc"; + + final static String APPENDER_WALLET = "wallet"; + + final static String APPENDER_DB = "db"; + + final static String APPENDER_STATE = "state"; + + final static String APPENDER_PENDING = "pending"; + + final static String APPENDER_MINE = "mine"; + + static void showWarn(String message, String... messages) { + + LoggerFactory.getLogger(APPENDER_PLATIN).warn(message); + final String ANSI_RED = "\u001B[31m"; + final String ANSI_RESET = "\u001B[0m"; + + System.err.println(ANSI_RED); + System.err.println(""); + System.err.println(" " + message); + for (String msg : messages) { + System.err.println(" " + msg); + } + System.err.println(""); + System.err.println(ANSI_RESET); + } + + static void showErrorAndExit(String message, String... messages) { + + LoggerFactory.getLogger(APPENDER_PLATIN).error(message); + + + final String ANSI_RED = "\u001B[31m"; + final String ANSI_RESET = "\u001B[0m"; + + System.err.println(ANSI_RED); + System.err.println(""); + System.err.println(" " + message); + for (String msg : messages) { + System.err.println(" " + msg); + } + System.err.println(""); + System.err.println(ANSI_RESET); + + throw new RuntimeException(message); + } + +} diff --git a/common/src/main/java/org/platon/common/BasicPbCodec.java b/common/src/main/java/org/platon/common/BasicPbCodec.java new file mode 100644 index 0000000..e09be63 --- /dev/null +++ b/common/src/main/java/org/platon/common/BasicPbCodec.java @@ -0,0 +1,84 @@ +package org.platon.common; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.proto.BaseProto; +import org.platon.common.utils.ByteArrayWrapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * base encode and decode by protoBuf + * @author yanze + */ +public class BasicPbCodec { + + public static byte[] encodeBytesList(byte[]... elements){ + if(elements == null){ + return null; + } + BaseProto.BaseBytesList.Builder builder = BaseProto.BaseBytesList.newBuilder(); + for(byte[] element : elements){ + builder.addBytesListData(ByteString.copyFrom(element)); + } + return builder.build().toByteArray(); + } + + public static byte[] encodeBytesList(List bytesList){ + if(bytesList == null){ + return null; + } + BaseProto.BaseBytesList.Builder builder = BaseProto.BaseBytesList.newBuilder(); + for(int i = 0;i decodeBytesList(byte[] protoBuf) throws InvalidProtocolBufferException { + BaseProto.BaseBytesList baseBytesList = BaseProto.BaseBytesList.parseFrom(protoBuf); + List list = new ArrayList<>(); + for(int i = 0;i { + private static Logger logger = LoggerFactory.getLogger(DelayCache.class); + + private ConcurrentMap cache = new ConcurrentHashMap(); + + private DelayQueue q = new DelayQueue(); + + private TimeoutCallbackFunction timeoutCallback; + + private Thread daemonThread; + + public DelayCache(TimeoutCallbackFunction timeoutCallback) { + this(); + this.timeoutCallback = timeoutCallback; + } + public DelayCache() { + + Runnable daemonTask = new Runnable() { + public void run() { + daemonCheck(); + } + }; + + daemonThread = new Thread(daemonTask); + daemonThread.setDaemon(true); + daemonThread.setName("Cache Daemon"); + daemonThread.start(); + } + + public int getSize(){ + return cache.size(); + } + + private void daemonCheck() { + for (;;) { + try { + DelayItem delayItem = q.take(); + V value = cache.remove(delayItem.getItem()); + if (timeoutCallback != null) { + //V value = cache.get((K)delayItem.getItem()); + timeoutCallback.timeout(delayItem.getItem(), value); + } + //cache.remove((K)delayItem.getItem()); + } catch (InterruptedException e) { + logger.error("error:", e); + break; + } + } + } + + public synchronized void put(K key, V value, long time, TimeUnit unit) { + V oldValue = cache.putIfAbsent(key, value); + if (oldValue != null) { + q.remove(new DelayItem(key, time, unit)); + } + q.put(new DelayItem(key, time, unit)); + } + + + public synchronized V remove(K key) { + V oldValue = cache.remove(key); + q.remove(new DelayItem(key, 1, TimeUnit.SECONDS)); + return oldValue; + } + + public V get(K key) { + return cache.get(key); + } + + + public void setTimeoutCallback(TimeoutCallbackFunction timeoutCallback) { + this.timeoutCallback = timeoutCallback; + } + + public interface TimeoutCallbackFunction { + void timeout(K key, V value); + } + + + public static void main(String[] args) throws InterruptedException { + DelayCache testCache = new DelayCache<>(); + testCache.setTimeoutCallback((key, value)->{ + System.out.println("key:" + key); + System.out.println("value:" + value); + }); + + testCache.put("key1", 1, 3, TimeUnit.SECONDS); + + testCache.remove("key1"); + + TimeUnit.SECONDS.sleep(100); + + + } + + +} diff --git a/common/src/main/java/org/platon/common/cache/DelayItem.java b/common/src/main/java/org/platon/common/cache/DelayItem.java new file mode 100644 index 0000000..5fc8927 --- /dev/null +++ b/common/src/main/java/org/platon/common/cache/DelayItem.java @@ -0,0 +1,92 @@ +package org.platon.common.cache; + +import java.util.Objects; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + + * @author lvxy + * @version 0.0.1 + * @date 2018/8/28 13:46 + */ +public class DelayItem implements Delayed { + + /** + * Base of nanosecond timings, to avoid wrapping + */ + private static final long NANO_ORIGIN = System.nanoTime(); + + /** + * Returns nanosecond time offset by origin + */ + final static long now() { + return System.nanoTime() - NANO_ORIGIN; + } + + /** + * Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied + * entries. + */ + private static final AtomicLong sequencer = new AtomicLong(0); + + /** + * Sequence number to break ties FIFO + */ + private final long sequenceNumber; + + /** + * The time the task is enabled to execute in nanoTime units + */ + private final long time; + + private final T item; + + public DelayItem(T submit, long time, TimeUnit unit) { + this.time = now() + TimeUnit.NANOSECONDS.convert(time, unit); + this.item = submit; + this.sequenceNumber = sequencer.getAndIncrement(); + } + + public T getItem() { + return this.item; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DelayItem delayItem = (DelayItem) o; + return Objects.equals(item, delayItem.item); + } + + @Override + public int hashCode() { + return Objects.hash(item); + } + + public long getDelay(TimeUnit unit) { + long d = unit.convert(time - now(), TimeUnit.NANOSECONDS); + return d; + } + + public int compareTo(Delayed other) { + if (other == this) // compare zero ONLY if same object + return 0; + if (other instanceof DelayItem) { + DelayItem x = (DelayItem) other; + long diff = time - x.time; + if (diff < 0) + return -1; + else if (diff > 0) + return 1; + else if (sequenceNumber < x.sequenceNumber) + return -1; + else + return 1; + } + long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS)); + return (d == 0) ? 0 : ((d < 0) ? -1 : 1); + } +} diff --git a/common/src/main/java/org/platon/common/config/ConfigProperties.java b/common/src/main/java/org/platon/common/config/ConfigProperties.java new file mode 100644 index 0000000..ae99ccb --- /dev/null +++ b/common/src/main/java/org/platon/common/config/ConfigProperties.java @@ -0,0 +1,90 @@ +package org.platon.common.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigRenderOptions; +import org.platon.common.AppenderName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +public class ConfigProperties { + + private static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + private static String configPath = null; + + private static final String configName = "platon.conf"; + + public static ConfigProperties getInstance() { + return ConfigProperties.SingletonContainer.instance; + } + + private static class SingletonContainer { + private static ConfigProperties instance = new ConfigProperties(); + } + + private Config config; + + private ClassLoader classLoader; + + public ConfigProperties() { + this(ConfigFactory.empty()); + } + + public ConfigProperties(File configFile) { + this(ConfigFactory.parseFile(configFile)); + } + + public ConfigProperties(String configResource) { + this(ConfigFactory.parseResources(configResource)); + } + + public ConfigProperties(Config apiConfig) { + this(apiConfig, ConfigProperties.class.getClassLoader()); + } + + public ConfigProperties(Config apiConfig, ClassLoader classLoader) { + try { + this.classLoader = classLoader; + + String fileName = getFullConfigFileName(); + File file = new File(fileName); + if (file.exists()) { + config = ConfigFactory.parseFile(file); + } else { + config = ConfigFactory.parseResources("./config/" + configName); + } + + logger.debug("Config trace: " + config.root().render(ConfigRenderOptions.defaults().setComments(false).setJson(false))); + } catch (Exception e) { + logger.error("Can't read config.", e); + throw new RuntimeException(e); + } + } + + private static String getFullConfigFileName() { + return getConfigPath() + File.separator + configName; + } + + public static void setConfigPath(String _configPath) { + configPath = _configPath; + } + + private static String getConfigPath() { + if (configPath == null) { + if (System.getProperty("config.dir") != null && !System.getProperty("config.dir").trim().isEmpty()) { + return System.getProperty("config.dir"); + } else { + return System.getProperty("user.dir") + File.separator + "config"; + } + } + return configPath; + } + + public Config getConfig() { + return config; + } + +} diff --git a/common/src/main/java/org/platon/common/config/IgnoreLoad.java b/common/src/main/java/org/platon/common/config/IgnoreLoad.java new file mode 100644 index 0000000..53f4458 --- /dev/null +++ b/common/src/main/java/org/platon/common/config/IgnoreLoad.java @@ -0,0 +1,4 @@ +package org.platon.common.config; + +public @interface IgnoreLoad { +} diff --git a/common/src/main/java/org/platon/common/config/NodeConfig.java b/common/src/main/java/org/platon/common/config/NodeConfig.java new file mode 100644 index 0000000..96159d2 --- /dev/null +++ b/common/src/main/java/org/platon/common/config/NodeConfig.java @@ -0,0 +1,31 @@ +package org.platon.common.config; + +import com.typesafe.config.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NodeConfig { + private static Logger logger = LoggerFactory.getLogger(NodeConfig.class); + + private Config config; + + public static NodeConfig getInstance() { + return new NodeConfig(ConfigProperties.getInstance().getConfig().getObject("node").toConfig()); + } + + private NodeConfig(Config config) { + this.config = config; + } + + public String getHost() { + return config.getString("host"); + } + + public String getPublicKey() { + return config.getString("public-key"); + } + + public String getPrivateKey() { + return config.getString("private-key"); + } +} diff --git a/common/src/main/java/org/platon/common/config/Validated.java b/common/src/main/java/org/platon/common/config/Validated.java new file mode 100644 index 0000000..e4c5eef --- /dev/null +++ b/common/src/main/java/org/platon/common/config/Validated.java @@ -0,0 +1,11 @@ +package org.platon.common.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Validated { +} diff --git a/common/src/main/java/org/platon/common/exceptions/DataDecodingException.java b/common/src/main/java/org/platon/common/exceptions/DataDecodingException.java new file mode 100644 index 0000000..88481f8 --- /dev/null +++ b/common/src/main/java/org/platon/common/exceptions/DataDecodingException.java @@ -0,0 +1,13 @@ +package org.platon.common.exceptions; + + +public class DataDecodingException extends RuntimeException { + + public DataDecodingException(String message) { + super(message); + } + + public DataDecodingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/org/platon/common/exceptions/DataEncodingException.java b/common/src/main/java/org/platon/common/exceptions/DataEncodingException.java new file mode 100644 index 0000000..cb2653c --- /dev/null +++ b/common/src/main/java/org/platon/common/exceptions/DataEncodingException.java @@ -0,0 +1,13 @@ +package org.platon.common.exceptions; + + +public class DataEncodingException extends RuntimeException { + + public DataEncodingException(String message) { + super(message); + } + + public DataEncodingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/org/platon/common/utils/BIUtil.java b/common/src/main/java/org/platon/common/utils/BIUtil.java new file mode 100644 index 0000000..d2b2bd6 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/BIUtil.java @@ -0,0 +1,114 @@ +package org.platon.common.utils; + +import java.math.BigInteger; + +public class BIUtil { + + /** + * @param value - not null + * @return true - if the param is zero + */ + public static boolean isZero(BigInteger value){ + return value.compareTo(BigInteger.ZERO) == 0; + } + + /** + * @param valueA - not null + * @param valueB - not null + * @return true - if the valueA is equal to valueB is zero + */ + public static boolean isEqual(BigInteger valueA, BigInteger valueB){ + return valueA.compareTo(valueB) == 0; + } + + /** + * @param valueA - not null + * @param valueB - not null + * @return true - if the valueA is not equal to valueB is zero + */ + public static boolean isNotEqual(BigInteger valueA, BigInteger valueB){ + return !isEqual(valueA, valueB); + } + + /** + * @param valueA - not null + * @param valueB - not null + * @return true - if the valueA is less than valueB is zero + */ + public static boolean isLessThan(BigInteger valueA, BigInteger valueB){ + return valueA.compareTo(valueB) < 0; + } + + /** + * @param valueA - not null + * @param valueB - not null + * @return true - if the valueA is more than valueB is zero + */ + public static boolean isMoreThan(BigInteger valueA, BigInteger valueB){ + return valueA.compareTo(valueB) > 0; + } + + + /** + * @param valueA - not null + * @param valueB - not null + * @return sum - valueA + valueB + */ + public static BigInteger sum(BigInteger valueA, BigInteger valueB){ + return valueA.add(valueB); + } + + + /** + * @param data = not null + * @return new positive BigInteger + */ + public static BigInteger toBI(byte[] data){ + return new BigInteger(1, data); + } + + /** + * @param data = not null + * @return new positive BigInteger + */ + public static BigInteger toBI(long data){ + return BigInteger.valueOf(data); + } + + + public static boolean isPositive(BigInteger value){ + return value.signum() > 0; + } + + public static boolean isCovers(BigInteger covers, BigInteger value){ + return !isNotCovers(covers, value); + } + + public static boolean isNotCovers(BigInteger covers, BigInteger value){ + return covers.compareTo(value) < 0; + } + + public static boolean exitLong(BigInteger value){ + + return (value.compareTo(new BigInteger(Long.MAX_VALUE + ""))) > -1; + } + + public static boolean isIn20PercentRange(BigInteger first, BigInteger second) { + BigInteger five = BigInteger.valueOf(5); + BigInteger limit = first.add(first.divide(five)); + return !isMoreThan(second, limit); + } + + public static BigInteger max(BigInteger first, BigInteger second) { + return first.compareTo(second) < 0 ? second : first; + } + + /** + * Returns a result of safe addition of two {@code int} values + * {@code Integer.MAX_VALUE} is returned if overflow occurs + */ + public static int addSafely(int a, int b) { + long res = (long) a + (long) b; + return res > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) res; + } +} diff --git a/common/src/main/java/org/platon/common/utils/ByteArrayMap.java b/common/src/main/java/org/platon/common/utils/ByteArrayMap.java new file mode 100644 index 0000000..f259e02 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/ByteArrayMap.java @@ -0,0 +1,192 @@ +package org.platon.common.utils; + +import java.util.*; + +/** + * @author - Jungle + * @version 0.0.1 + * @date 2018/9/14 17:39 + */ +public class ByteArrayMap implements Map { + + // tips: 委托代理对象 + private final Map delegate; + + public ByteArrayMap() { + this(new HashMap()); + } + + public ByteArrayMap(Map delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public V put(byte[] key, V value) { + return delegate.put(new ByteArrayWrapper(key), value); + } + + @Override + public V remove(Object key) { + return delegate.remove(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + delegate.put(new ByteArrayWrapper(entry.getKey()), entry.getValue()); + } + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return new ByteArraySet(new SetAdapter<>(delegate)); + } + + @Override + public Collection values() { + return delegate.values(); + } + + @Override + public Set> entrySet() { + return new MapEntrySet(delegate.entrySet()); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + private class MapEntrySet implements Set> { + + private final Set> delegate; + + private MapEntrySet(Set> delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public Iterator> iterator() { + final Iterator> it = delegate.iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public Entry next() { + Entry next = it.next(); + return new AbstractMap.SimpleImmutableEntry(next.getKey().getData(), next.getValue()); + } + + @Override + public void remove() { + it.remove(); + } + }; + } + + @Override + public Object[] toArray() { + throw new RuntimeException("Not implemented"); + } + + @Override + public T[] toArray(T[] a) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean add(Entry vEntry) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean remove(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean containsAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean addAll(Collection> c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean removeAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public void clear() { + throw new RuntimeException("Not implemented"); + + } + } +} diff --git a/common/src/main/java/org/platon/common/utils/ByteArraySet.java b/common/src/main/java/org/platon/common/utils/ByteArraySet.java new file mode 100644 index 0000000..9ee0841 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/ByteArraySet.java @@ -0,0 +1,125 @@ +package org.platon.common.utils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class ByteArraySet implements Set { + + Set delegate; + + public ByteArraySet() { + this(new HashSet()); + } + + ByteArraySet(Set delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(new ByteArrayWrapper((byte[]) o)); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + Iterator it = delegate.iterator(); + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public byte[] next() { + return it.next().getData(); + } + + @Override + public void remove() { + it.remove(); + } + }; + } + + @Override + public Object[] toArray() { + byte[][] ret = new byte[size()][]; + + ByteArrayWrapper[] arr = delegate.toArray(new ByteArrayWrapper[size()]); + for (int i = 0; i < arr.length; i++) { + ret[i] = arr[i].getData(); + } + return ret; + } + + @Override + public T[] toArray(T[] a) { + return (T[]) toArray(); + } + + @Override + public boolean add(byte[] bytes) { + return delegate.add(new ByteArrayWrapper(bytes)); + } + + @Override + public boolean remove(Object o) { + return delegate.remove(new ByteArrayWrapper((byte[]) o)); + } + + @Override + public boolean containsAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean addAll(Collection c) { + boolean ret = false; + for (byte[] bytes : c) { + ret |= add(bytes); + } + return ret; + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean removeAll(Collection c) { + boolean changed = false; + for (Object el : c) { + changed |= remove(el); + } + return changed; + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("Not implemented"); + } +} diff --git a/common/src/main/java/org/platon/common/utils/ByteArrayWrapper.java b/common/src/main/java/org/platon/common/utils/ByteArrayWrapper.java new file mode 100644 index 0000000..d292b70 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/ByteArrayWrapper.java @@ -0,0 +1,53 @@ +package org.platon.common.utils; + +import java.io.Serializable; +import java.util.Arrays; + +import static org.platon.common.utils.Numeric.toHexString; + + +public class ByteArrayWrapper implements Comparable, Serializable { + + private final byte[] data; + private int hashCode = 0; + + public ByteArrayWrapper(byte[] data) { + if (null == data) { + throw new NullPointerException("data required."); + } + this.data = data; + // hashCode用于比较 + this.hashCode = Arrays.hashCode(data); + } + + public boolean equals(Object other) { + if (!(other instanceof ByteArrayWrapper)) { + return false; + } + byte[] otherData = ((ByteArrayWrapper) other).getData(); + return ByteComparator.compareTo( + data, 0, data.length, + otherData, 0, otherData.length) == 0; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public int compareTo(ByteArrayWrapper o) { + return ByteComparator.compareTo( + data, 0, data.length, + o.getData(), 0, o.getData().length); + } + + public byte[] getData() { + return data; + } + + @Override + public String toString() { + return toHexString(data); + } +} diff --git a/common/src/main/java/org/platon/common/utils/ByteComparator.java b/common/src/main/java/org/platon/common/utils/ByteComparator.java new file mode 100644 index 0000000..188ca9f --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/ByteComparator.java @@ -0,0 +1,46 @@ +package org.platon.common.utils; + +public final class ByteComparator { + + public static boolean equals(byte[] b1, byte[] b2) { + return b1.length == b2.length && compareTo(b1, 0, b1.length, b2, 0, b2.length) == 0; + } + + public static int compareTo(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) { + return lexicographicalComparerJavaImpl().compareTo( + b1, s1, l1, b2, s2, l2); + } + + private interface Comparer { + int compareTo(T buffer1, int offset1, int length1, + T buffer2, int offset2, int length2); + } + + private static Comparer lexicographicalComparerJavaImpl() { + return PureJavaComparer.INSTANCE; + } + + private enum PureJavaComparer implements Comparer { + + INSTANCE; + + @Override + public int compareTo(byte[] buffer1, int offset1, int length1, + byte[] buffer2, int offset2, int length2) { + + if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) { + return 0; + } + int end1 = offset1 + length1; + int end2 = offset2 + length2; + for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) { + int a = (buffer1[i] & 0xff); + int b = (buffer2[j] & 0xff); + if (a != b) { + return a - b; + } + } + return length1 - length2; + } + } +} diff --git a/common/src/main/java/org/platon/common/utils/ByteUtil.java b/common/src/main/java/org/platon/common/utils/ByteUtil.java new file mode 100644 index 0000000..3160107 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/ByteUtil.java @@ -0,0 +1,711 @@ +package org.platon.common.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.*; + +public class ByteUtil { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final byte[] ZERO_BYTE_ARRAY = new byte[]{0}; + + /** + * Creates a copy of bytes and appends b to the end of it + */ + public static byte[] appendByte(byte[] bytes, byte b) { + byte[] result = Arrays.copyOf(bytes, bytes.length + 1); + result[result.length - 1] = b; + return result; + } + + /** + * The regular {@link BigInteger#toByteArray()} method isn't quite what we often need: + * it appends a leading zero to indicate that the number is positive and may need padding. + * + * @param b the integer to format into a byte array + * @param numBytes the desired size of the resulting byte array + * @return numBytes byte long array. + */ + public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) { + if (b == null) + return null; + byte[] bytes = new byte[numBytes]; + byte[] biBytes = b.toByteArray(); + int start = (biBytes.length == numBytes + 1) ? 1 : 0; + int length = Math.min(biBytes.length, numBytes); + System.arraycopy(biBytes, start, bytes, numBytes - length, length); + return bytes; + } + + public static byte[] bigIntegerToBytesSigned(BigInteger b, int numBytes) { + if (b == null) + return null; + byte[] bytes = new byte[numBytes]; + Arrays.fill(bytes, b.signum() < 0 ? (byte) 0xFF : 0x00); + byte[] biBytes = b.toByteArray(); + int start = (biBytes.length == numBytes + 1) ? 1 : 0; + int length = Math.min(biBytes.length, numBytes); + System.arraycopy(biBytes, start, bytes, numBytes - length, length); + return bytes; + } + + /** + * Omitting sign indication byte. + * + * @param value - any big integer number. A null-value will return null + * @return A byte array without a leading zero byte if present in the signed encoding. + * BigInteger.ZERO will return an array with length 1 and byte-value 0. + */ + public static byte[] bigIntegerToBytes(BigInteger value) { + if (value == null) + return null; + + byte[] data = value.toByteArray(); + + if (data.length != 1 && data[0] == 0) { + byte[] tmp = new byte[data.length - 1]; + System.arraycopy(data, 1, tmp, 0, tmp.length); + data = tmp; + } + return data; + } + + /** + * Cast hex encoded value from byte[] to BigInteger + * null is parsed like byte[0] + * + * @param bb byte array contains the values + * @return unsigned positive BigInteger value. + */ + public static BigInteger bytesToBigInteger(byte[] bb) { + return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb); + } + + /** + * Returns the amount of nibbles that match each other from 0 ... + * amount will never be larger than smallest input + * + * @param a - first input + * @param b - second input + * @return Number of bytes that match + */ + public static int matchingNibbleLength(byte[] a, byte[] b) { + int i = 0; + int length = a.length < b.length ? a.length : b.length; + while (i < length) { + if (a[i] != b[i]) + return i; + i++; + } + return i; + } + + /** + * Converts a long value into a byte array. + * + * @param val - long value to convert + * @return byte[] of length 8, representing the long value + */ + public static byte[] longToBytes(long val) { + return ByteBuffer.allocate(Long.BYTES).putLong(val).array(); + } + + /** + * Converts a long value into a byte array. + * + * @param val - long value to convert + * @return decimal value with leading byte that are zeroes striped + */ + public static byte[] longToBytesNoLeadZeroes(long val) { + + // todo: improve performance by while strip numbers until (long >> 8 == 0) + if (val == 0) return EMPTY_BYTE_ARRAY; + + byte[] data = ByteBuffer.allocate(Long.BYTES).putLong(val).array(); + + return stripLeadingZeroes(data); + } + + /** + * Converts int value into a byte array. + * + * @param val - int value to convert + * @return byte[] of length 4, representing the int value + */ + public static byte[] intToBytes(int val) { + return ByteBuffer.allocate(Integer.BYTES).putInt(val).array(); + } + + /** + * Converts a int value into a byte array. + * + * @param val - int value to convert + * @return value with leading byte that are zeroes striped + */ + public static byte[] intToBytesNoLeadZeroes(int val) { + + if (val == 0) return EMPTY_BYTE_ARRAY; + + int lenght = 0; + + int tmpVal = val; + while (tmpVal != 0) { + tmpVal = tmpVal >>> 8; + ++lenght; + } + + byte[] result = new byte[lenght]; + + int index = result.length - 1; + while (val != 0) { + + result[index] = (byte) (val & 0xFF); + val = val >>> 8; + index -= 1; + } + + return result; + } + + /** + * Calculate packet length + * + * @param msg byte[] + * @return byte-array with 4 elements + */ + public static byte[] calcPacketLength(byte[] msg) { + int msgLen = msg.length; + return new byte[]{ + (byte) ((msgLen >> 24) & 0xFF), + (byte) ((msgLen >> 16) & 0xFF), + (byte) ((msgLen >> 8) & 0xFF), + (byte) ((msgLen) & 0xFF)}; + } + + /** + * Cast hex encoded value from byte[] to int + * null is parsed like byte[0] + *

+ * Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) + * + * @param b array contains the values + * @return unsigned positive int value. + */ + public static int byteArrayToInt(byte[] b) { + if (b == null || b.length == 0) + return 0; + return new BigInteger(1, b).intValue(); + } + + /** + * Cast hex encoded value from byte[] to long + * null is parsed like byte[0] + *

+ * Limited to Long.MAX_VALUE: 263-1 (8 bytes) + * + * @param b array contains the values + * @return unsigned positive long value. + */ + public static long byteArrayToLong(byte[] b) { + if (b == null || b.length == 0) + return 0; + return new BigInteger(1, b).longValue(); + } + + /** + * Turn nibbles to a pretty looking output string + *

+ * Example. [ 1, 2, 3, 4, 5 ] becomes '\x11\x23\x45' + * + * @param nibbles - getting byte of data [ 04 ] and turning + * it to a '\x04' representation + * @return pretty string of nibbles + */ + public static String nibblesToPrettyString(byte[] nibbles) { + StringBuilder builder = new StringBuilder(); + for (byte nibble : nibbles) { + final String nibbleString = oneByteToHexString(nibble); + builder.append("\\x").append(nibbleString); + } + return builder.toString(); + } + + public static String oneByteToHexString(byte value) { + String retVal = Integer.toString(value & 0xFF, 16); + if (retVal.length() == 1) retVal = "0" + retVal; + return retVal; + } + + /** + * Calculate the number of bytes need + * to encode the number + * + * @param val - number + * @return number of min bytes used to encode the number + */ + public static int numBytes(String val) { + + BigInteger bInt = new BigInteger(val); + int bytes = 0; + + while (!bInt.equals(BigInteger.ZERO)) { + bInt = bInt.shiftRight(8); + ++bytes; + } + if (bytes == 0) ++bytes; + return bytes; + } + + /** + * @param arg - not more that 32 bits + * @return - bytes of the value pad with complete to 32 zeroes + */ + public static byte[] encodeValFor32Bits(Object arg) { + + byte[] data; + + // check if the string is numeric + if (arg.toString().trim().matches("-?\\d+(\\.\\d+)?")) + data = new BigInteger(arg.toString().trim()).toByteArray(); + // check if it's hex number + else if (arg.toString().trim().matches("0[xX][0-9a-fA-F]+")) + data = new BigInteger(arg.toString().trim().substring(2), 16).toByteArray(); + else + data = arg.toString().trim().getBytes(); + + if (data.length > 32) + throw new RuntimeException("values can't be more than 32 byte"); + + byte[] val = new byte[32]; + + int j = 0; + for (int i = data.length; i > 0; --i) { + val[31 - j] = data[i - 1]; + ++j; + } + return val; + } + + /** + * encode the values and concatenate together + * + * @param args Object + * @return byte[] + */ + public static byte[] encodeDataList(Object... args) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (Object arg : args) { + byte[] val = encodeValFor32Bits(arg); + try { + baos.write(val); + } catch (IOException e) { + throw new Error("Happen something that should never happen ", e); + } + } + return baos.toByteArray(); + } + + public static int firstNonZeroByte(byte[] data) { + for (int i = 0; i < data.length; ++i) { + if (data[i] != 0) { + return i; + } + } + return -1; + } + + public static byte[] stripLeadingZeroes(byte[] data) { + + if (data == null) + return null; + + final int firstNonZero = firstNonZeroByte(data); + switch (firstNonZero) { + case -1: + return ZERO_BYTE_ARRAY; + + case 0: + return data; + + default: + byte[] result = new byte[data.length - firstNonZero]; + System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero); + + return result; + } + } + + /** + * increment byte array as a number until max is reached + * + * @param bytes byte[] + * @return boolean + */ + public static boolean increment(byte[] bytes) { + final int startIndex = 0; + int i; + for (i = bytes.length - 1; i >= startIndex; i--) { + bytes[i]++; + if (bytes[i] != 0) + break; + } + // we return false when all bytes are 0 again + return (i >= startIndex || bytes[startIndex] != 0); + } + + /** + * Utility function to copy a byte array into a new byte array with given size. + * If the src length is smaller than the given size, the result will be left-padded + * with zeros. + * + * @param value - a BigInteger with a maximum value of 2^256-1 + * @return Byte array of given size with a copy of the src + */ + public static byte[] copyToArray(BigInteger value) { + byte[] src = ByteUtil.bigIntegerToBytes(value); + byte[] dest = ByteBuffer.allocate(32).array(); + System.arraycopy(src, 0, dest, dest.length - src.length, src.length); + return dest; + } + + public static byte[] setBit(byte[] data, int pos, int val) { + + if ((data.length * 8) - 1 < pos) + throw new Error("outside byte array limit, pos: " + pos); + + int posByte = data.length - 1 - (pos) / 8; + int posBit = (pos) % 8; + byte setter = (byte) (1 << (posBit)); + byte toBeSet = data[posByte]; + byte result; + if (val == 1) + result = (byte) (toBeSet | setter); + else + result = (byte) (toBeSet & ~setter); + + data[posByte] = result; + return data; + } + + public static int getBit(byte[] data, int pos) { + + if ((data.length * 8) - 1 < pos) + throw new Error("outside byte array limit, pos: " + pos); + + int posByte = data.length - 1 - pos / 8; + int posBit = pos % 8; + byte dataByte = data[posByte]; + return Math.min(1, (dataByte & (1 << (posBit)))); + } + + public static byte[] and(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] & b2[i]); + } + return ret; + } + + public static byte[] or(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] | b2[i]); + } + return ret; + } + + public static byte[] xor(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] ^ b2[i]); + } + return ret; + } + + /** + * XORs byte arrays of different lengths by aligning length of the shortest via adding zeros at beginning + */ + public static byte[] xorAlignRight(byte[] b1, byte[] b2) { + if (b1.length > b2.length) { + byte[] b2_ = new byte[b1.length]; + System.arraycopy(b2, 0, b2_, b1.length - b2.length, b2.length); + b2 = b2_; + } else if (b2.length > b1.length) { + byte[] b1_ = new byte[b2.length]; + System.arraycopy(b1, 0, b1_, b2.length - b1.length, b1.length); + b1 = b1_; + } + + return xor(b1, b2); + } + + /** + * @param arrays - arrays to merge + * @return - merged array + */ + public static byte[] merge(byte[]... arrays) { + int count = 0; + for (byte[] array : arrays) { + count += array.length; + } + + // Create new array and copy all array contents + byte[] mergedArray = new byte[count]; + int start = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, mergedArray, start, array.length); + start += array.length; + } + return mergedArray; + } + + public static boolean isNullOrZeroArray(byte[] array) { + return (array == null) || (array.length == 0); + } + + public static boolean isSingleZero(byte[] array) { + return (array.length == 1 && array[0] == 0); + } + + public static Set difference(Set setA, Set setB) { + + Set result = new HashSet<>(); + + for (byte[] elementA : setA) { + boolean found = false; + for (byte[] elementB : setB) { + + if (Arrays.equals(elementA, elementB)) { + found = true; + break; + } + } + if (!found) result.add(elementA); + } + + return result; + } + + public static int length(byte[]... bytes) { + int result = 0; + for (byte[] array : bytes) { + result += (array == null) ? 0 : array.length; + } + return result; + } + + public static byte[] intsToBytes(int[] arr, boolean bigEndian) { + byte[] ret = new byte[arr.length * 4]; + intsToBytes(arr, ret, bigEndian); + return ret; + } + + public static int[] bytesToInts(byte[] arr, boolean bigEndian) { + int[] ret = new int[arr.length / 4]; + bytesToInts(arr, ret, bigEndian); + return ret; + } + + public static void bytesToInts(byte[] b, int[] arr, boolean bigEndian) { + if (!bigEndian) { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = b[off++] & 0x000000FF; + ii |= (b[off++] << 8) & 0x0000FF00; + ii |= (b[off++] << 16) & 0x00FF0000; + ii |= (b[off++] << 24); + arr[i] = ii; + } + } else { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = b[off++] << 24; + ii |= (b[off++] << 16) & 0x00FF0000; + ii |= (b[off++] << 8) & 0x0000FF00; + ii |= b[off++] & 0x000000FF; + arr[i] = ii; + } + } + } + + public static void intsToBytes(int[] arr, byte[] b, boolean bigEndian) { + if (!bigEndian) { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = arr[i]; + b[off++] = (byte) (ii & 0xFF); + b[off++] = (byte) ((ii >> 8) & 0xFF); + b[off++] = (byte) ((ii >> 16) & 0xFF); + b[off++] = (byte) ((ii >> 24) & 0xFF); + } + } else { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = arr[i]; + b[off++] = (byte) ((ii >> 24) & 0xFF); + b[off++] = (byte) ((ii >> 16) & 0xFF); + b[off++] = (byte) ((ii >> 8) & 0xFF); + b[off++] = (byte) (ii & 0xFF); + } + } + } + + public static short bigEndianToShort(byte[] bs) { + return bigEndianToShort(bs, 0); + } + + public static short bigEndianToShort(byte[] bs, int off) { + int n = bs[off] << 8; + ++off; + n |= bs[off] & 0xFF; + return (short) n; + } + + public static byte[] shortToBytes(short n) { + return ByteBuffer.allocate(2).putShort(n).array(); + } + + /** + * Converts string representation of host/ip to 4-bytes byte[] IPv4 + */ + public static byte[] hostToBytes(String ip) { + byte[] bytesIp; + try { + bytesIp = InetAddress.getByName(ip).getAddress(); + } catch (UnknownHostException e) { + bytesIp = new byte[4]; // fall back to invalid 0.0.0.0 address + } + + return bytesIp; + } + + /** + * Converts 4 bytes IPv4 IP to String representation + */ + public static String bytesToIp(byte[] bytesIp) { + + StringBuilder sb = new StringBuilder(); + sb.append(bytesIp[0] & 0xFF); + sb.append("."); + sb.append(bytesIp[1] & 0xFF); + sb.append("."); + sb.append(bytesIp[2] & 0xFF); + sb.append("."); + sb.append(bytesIp[3] & 0xFF); + + String ip = sb.toString(); + return ip; + } + + /** + * Returns a number of zero bits preceding the highest-order ("leftmost") one-bit + * interpreting input array as a big-endian integer value + */ + public static int numberOfLeadingZeros(byte[] bytes) { + + int i = firstNonZeroByte(bytes); + + if (i == -1) { + return bytes.length * 8; + } else { + int byteLeadingZeros = Integer.numberOfLeadingZeros((int) bytes[i] & 0xff) - 24; + return i * 8 + byteLeadingZeros; + } + } + + /** + * Parses fixed number of bytes starting from {@code offset} in {@code input} array. + * If {@code input} has not enough bytes return array will be right padded with zero bytes. + * I.e. if {@code offset} is higher than {@code input.length} then zero byte array of length {@code len} will be returned + */ + public static byte[] parseBytes(byte[] input, int offset, int len) { + + if (offset >= input.length || len == 0) + return EMPTY_BYTE_ARRAY; + + byte[] bytes = new byte[len]; + System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len)); + return bytes; + } + + /** + * Parses 32-bytes word from given input. + * Uses {@link #parseBytes(byte[], int, int)} method, + * thus, result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + */ + public static byte[] parseWord(byte[] input, int idx) { + return parseBytes(input, 32 * idx, 32); + } + + /** + * Parses 32-bytes word from given input. + * Uses {@link #parseBytes(byte[], int, int)} method, + * thus, result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + * @param offset an offset in {@code input} array to start parsing from + */ + public static byte[] parseWord(byte[] input, int offset, int idx) { + return parseBytes(input, offset + 32 * idx, 32); + } + + /** + * construct the key stored in the extradb + * + * @param hash hash of block + * @param tail identifier + * @return the key + */ + public static byte[] toSlice(byte[] hash, byte tail) { + byte[] ret = new byte[hash.length + 1]; + System.arraycopy(hash, 0, ret, 0, hash.length); + ret[hash.length] = tail; + return ret; + } + + public static byte[] long2Bytes(long num) { + byte[] byteNum = new byte[8]; + for (int ix = 0; ix < 8; ++ix) { + int offset = 64 - (ix + 1) * 8; + byteNum[ix] = (byte) ((num >> offset) & 0xff); + } + return byteNum; + } + + public static long bytes2Long(byte[] byteNum) { + long num = 0; + for (int ix = 0; ix < 8; ++ix) { + num <<= 8; + num |= (byteNum[ix] & 0xff); + } + return num; + } + + /** + * check if the array contains the byte[] + * + * @param array the array + * @param key the key + * @return true or false + */ + public static boolean contains(Collection array, byte[] key) { + + Iterator it = array.iterator(); + while (it.hasNext()) { + byte[] item = it.next(); + if (Arrays.equals(item, key)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/platon/common/utils/FileUtil.java b/common/src/main/java/org/platon/common/utils/FileUtil.java new file mode 100644 index 0000000..ad43503 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/FileUtil.java @@ -0,0 +1,60 @@ +package org.platon.common.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +public class FileUtil { + + public static List recursiveList(String path) throws IOException { + + final List files = new ArrayList<>(); + + Files.walkFileTree(Paths.get(path), new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + files.add(file.toString()); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + + return files; + } + + public static boolean recursiveDelete(String fileName) { + File file = new File(fileName); + if (file.exists()) { + if (file.isDirectory()) { + if ((file.list()).length > 0) { + for(String s:file.list()){ + recursiveDelete(fileName + File.separator + s); + } + } + } + file.setWritable(true); + boolean result = file.delete(); + return result; + } else { + return false; + } + } +} diff --git a/common/src/main/java/org/platon/common/utils/LRUHashMap.java b/common/src/main/java/org/platon/common/utils/LRUHashMap.java new file mode 100644 index 0000000..f864cc4 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/LRUHashMap.java @@ -0,0 +1,88 @@ +package org.platon.common.utils; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author - yanze + * @date 2018/9/14 16:36 + * @version 0.0.1 + */ +public class LRUHashMap extends LinkedHashMap { + + private long maxCapacity; + + private final Lock lock = new ReentrantLock(); + + private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + + + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + + public LRUHashMap(long size) { + super(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR,true); + this.maxCapacity = size; + } + + @Override + public boolean containsValue(Object value) { + return super.containsValue(value); + } + + @Override + public V get(Object key) { + try { + lock.lock(); + return super.get(key); + } finally { + lock.unlock(); + } + } + + @Override + public V put(K key, V value) { + try { + lock.lock(); + return super.put(key, value); + } finally { + lock.unlock(); + } + } + + @Override + public void putAll(Map m) { + try { + lock.lock(); + super.putAll(m); + } finally { + lock.unlock(); + } + } + + @Override + public V remove(Object key) { + try { + lock.lock(); + return super.remove(key); + } finally { + lock.unlock(); + } + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > this.maxCapacity; + } + + public long getMaxCapacity() { + return maxCapacity; + } + + public void setMaxCapacity(long maxCapacity) { + this.maxCapacity = maxCapacity; + } + +} diff --git a/common/src/main/java/org/platon/common/utils/Numeric.java b/common/src/main/java/org/platon/common/utils/Numeric.java new file mode 100644 index 0000000..4e89157 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/Numeric.java @@ -0,0 +1,214 @@ +package org.platon.common.utils; + + +import org.platon.common.exceptions.DataDecodingException; +import org.platon.common.exceptions.DataEncodingException; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; + +public final class Numeric { + + private static final String HEX_PREFIX = "0x"; + + private Numeric() { + } + + public static String encodeQuantity(BigInteger value) { + if (value.signum() != -1) { + return HEX_PREFIX + value.toString(16); + } else { + throw new DataEncodingException("Negative values are not supported."); + } + } + + public static BigInteger decodeQuantity(String value) { + if (!isValidHexQuantity(value)) { + throw new DataDecodingException("Value must be in format 0x[1-9]+[0-9]* or 0x0"); + } + try { + return new BigInteger(value.substring(2), 16); + } catch (NumberFormatException e) { + throw new DataDecodingException("Negative, invalid data format ", e); + } + } + + private static boolean isValidHexQuantity(String value) { + if (value == null) { + return false; + } + if (value.length() < 3) { + return false; + } + if (!value.startsWith(HEX_PREFIX)) { + return false; + } + return true; + } + + public static String cleanHexPrefix(String input) { + if (containsHexPrefix(input)) { + return input.substring(2); + } else { + return input; + } + } + + public static String prependHexPrefix(String input) { + if (!containsHexPrefix(input)) { + return HEX_PREFIX + input; + } else { + return input; + } + } + + public static boolean containsHexPrefix(String input) { + return !StringEnhance.isEmpty(input) && input.length() > 1 + && input.charAt(0) == '0' && input.charAt(1) == 'x'; + } + + public static BigInteger toBigInt(byte[] value, int offset, int length) { + return toBigInt((Arrays.copyOfRange(value, offset, offset + length))); + } + + public static BigInteger toBigInt(byte[] value) { + return new BigInteger(1, value); + } + + public static BigInteger toBigInt(String hexValue) { + String cleanValue = cleanHexPrefix(hexValue); + return toBigIntNoPrefix(cleanValue); + } + + public static BigInteger toBigIntNoPrefix(String hexValue) { + return new BigInteger(hexValue, 16); + } + + public static String toHexStringWithPrefix(BigInteger value) { + return HEX_PREFIX + value.toString(16); + } + + public static String toHexStringNoPrefix(BigInteger value) { + return value.toString(16); + } + + public static String toHexStringNoPrefix(byte[] input) { + return toHexString(input, 0, input.length, false); + } + + public static String toHexStringWithPrefixZeroPadded(BigInteger value, int size) { + return toHexStringZeroPadded(value, size, true); + } + + public static String toHexStringWithPrefixSafe(BigInteger value) { + String result = toHexStringNoPrefix(value); + if (result.length() < 2) { + result = StringEnhance.zeros(1) + result; + } + return HEX_PREFIX + result; + } + + public static String toHexStringNoPrefixZeroPadded(BigInteger value, int size) { + return toHexStringZeroPadded(value, size, false); + } + + private static String toHexStringZeroPadded(BigInteger value, int size, boolean withPrefix) { + String result = toHexStringNoPrefix(value); + + int length = result.length(); + if (length > size) { + throw new UnsupportedOperationException( + "Value " + result + "is larger then length " + size); + } else if (value.signum() < 0) { + throw new UnsupportedOperationException("Value cannot be negative"); + } + + if (length < size) { + result = StringEnhance.zeros(size - length) + result; + } + + if (withPrefix) { + return HEX_PREFIX + result; + } else { + return result; + } + } + + public static byte[] toBytesPadded(BigInteger value, int length) { + byte[] result = new byte[length]; + byte[] bytes = value.toByteArray(); + + int bytesLength; + int srcOffset; + if (bytes[0] == 0) { + bytesLength = bytes.length - 1; + srcOffset = 1; + } else { + bytesLength = bytes.length; + srcOffset = 0; + } + + if (bytesLength > length) { + throw new RuntimeException("Input is too large to put in byte array of size " + length); + } + + int destOffset = length - bytesLength; + System.arraycopy(bytes, srcOffset, result, destOffset, bytesLength); + return result; + } + + public static byte[] hexStringToByteArray(String input) { + String cleanInput = cleanHexPrefix(input); + + int len = cleanInput.length(); + + if (len == 0) { + return new byte[] {}; + } + + byte[] data; + int startIdx; + if (len % 2 != 0) { + data = new byte[(len / 2) + 1]; + data[0] = (byte) Character.digit(cleanInput.charAt(0), 16); + startIdx = 1; + } else { + data = new byte[len / 2]; + startIdx = 0; + } + + for (int i = startIdx; i < len; i += 2) { + data[(i + 1) / 2] = (byte) ((Character.digit(cleanInput.charAt(i), 16) << 4) + + Character.digit(cleanInput.charAt(i + 1), 16)); + } + return data; + } + + public static String toHexString(byte[] input, int offset, int length, boolean withPrefix) { + StringBuilder stringBuilder = new StringBuilder(); + if (withPrefix) { + stringBuilder.append("0x"); + } + for (int i = offset; i < offset + length; i++) { + stringBuilder.append(String.format("%02x", input[i] & 0xFF)); + } + + return stringBuilder.toString(); + } + + public static String toHexString(byte[] input) { + return toHexString(input, 0, input.length, true); + } + + public static byte asByte(int m, int n) { + return (byte) ((m << 4) | n); + } + + public static boolean isIntegerValue(BigDecimal value) { + return value.signum() == 0 + || value.scale() <= 0 + || value.stripTrailingZeros().scale() <= 0; + } + +} diff --git a/common/src/main/java/org/platon/common/utils/RandomUtils.java b/common/src/main/java/org/platon/common/utils/RandomUtils.java new file mode 100644 index 0000000..a848d42 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/RandomUtils.java @@ -0,0 +1,22 @@ +package org.platon.common.utils; + +import java.util.Random; + + +public class RandomUtils { + + public static byte[] randomBytes(int length) { + byte[] result = new byte[length]; + new Random().nextBytes(result); + return result; + } + + + public static int randomInt(int maxInt) { + int result = 0; + while (result == 0){ + result = (int)(Math.random()*maxInt); + } + return result; + } +} diff --git a/common/src/main/java/org/platon/common/utils/SetAdapter.java b/common/src/main/java/org/platon/common/utils/SetAdapter.java new file mode 100644 index 0000000..53a3681 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/SetAdapter.java @@ -0,0 +1,90 @@ +package org.platon.common.utils; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class SetAdapter implements Set { + + private static final Object DummyValue = new Object(); + + Map delegate; + + public SetAdapter(Map delegate) { + this.delegate = (Map) delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.containsKey(o); + } + + @Override + public Iterator iterator() { + return delegate.keySet().iterator(); + } + + @Override + public Object[] toArray() { + return delegate.keySet().toArray(); + } + + @Override + public T[] toArray(T[] a) { + return delegate.keySet().toArray(a); + } + + @Override + public boolean add(E e) { + return delegate.put(e, DummyValue) == null; + } + + @Override + public boolean remove(Object o) { + return delegate.remove(o) != null; + } + + @Override + public boolean containsAll(Collection c) { + return delegate.keySet().containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + boolean ret = false; + for (E e : c) { + ret |= add(e); + } + return ret; + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean removeAll(Collection c) { + boolean ret = false; + for (Object e : c) { + ret |= remove(e); + } + return ret; + } + + @Override + public void clear() { + delegate.clear(); + } +} diff --git a/common/src/main/java/org/platon/common/utils/SpringContextUtil.java b/common/src/main/java/org/platon/common/utils/SpringContextUtil.java new file mode 100644 index 0000000..5a975fd --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/SpringContextUtil.java @@ -0,0 +1,33 @@ +package org.platon.common.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +@Component +@Configuration +@ComponentScan(basePackages={"org.platon"}) +public class SpringContextUtil implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + SpringContextUtil.applicationContext = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + public static T getBean(String name) throws BeansException { + return (T)applicationContext.getBean(name); + } + + public static T getBean(Class cls) throws BeansException { + return (T)applicationContext.getBean(cls); + } +} diff --git a/common/src/main/java/org/platon/common/utils/StringEnhance.java b/common/src/main/java/org/platon/common/utils/StringEnhance.java new file mode 100644 index 0000000..0d721f3 --- /dev/null +++ b/common/src/main/java/org/platon/common/utils/StringEnhance.java @@ -0,0 +1,46 @@ +package org.platon.common.utils; + +import java.util.List; + +/** + * @author - Jungle + * @version 0.0.1 + * @date 2018/9/14 16:49 + */ +public class StringEnhance { + + private StringEnhance() { + } + + public static String join(List src, String delimiter) { + return src == null ? null : String.join(delimiter, src.toArray(new String[0])); + } + + public static String capitaliseFirstLetter(String string) { + if (string == null || string.length() == 0) { + return string; + } else { + return string.substring(0, 1).toUpperCase() + string.substring(1); + } + } + + public static String lowercaseFirstLetter(String string) { + if (string == null || string.length() == 0) { + return string; + } else { + return string.substring(0, 1).toLowerCase() + string.substring(1); + } + } + + public static String zeros(int n) { + return repeat('0', n); + } + + public static String repeat(char value, int n) { + return new String(new char[n]).replace("\0", String.valueOf(value)); + } + + public static boolean isEmpty(String s) { + return s == null || s.length() == 0; + } +} diff --git a/common/src/main/java/org/platon/common/wrapper/DataWord.java b/common/src/main/java/org/platon/common/wrapper/DataWord.java new file mode 100644 index 0000000..3dc8e28 --- /dev/null +++ b/common/src/main/java/org/platon/common/wrapper/DataWord.java @@ -0,0 +1,405 @@ +package org.platon.common.wrapper; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteComparator; +import org.platon.common.utils.ByteUtil; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +import static org.platon.common.utils.ByteUtil.numberOfLeadingZeros; + +public final class DataWord implements Comparable { + + + public static final int MAX_POW = 256; + public static final BigInteger _2_256 = BigInteger.valueOf(2).pow(MAX_POW); + public static final BigInteger MAX_VALUE = _2_256.subtract(BigInteger.ONE); + public static final DataWord ZERO = new DataWord(new byte[32]); + public static final DataWord ONE = DataWord.of((byte) 1); + + public static final long MEM_SIZE = 32 + 16 + 16; + + private final byte[] data; + + + private DataWord(byte[] data) { + if (data == null || data.length != 32) throw new RuntimeException("Input byte array should have 32 bytes in it!"); + this.data = data; + } + + public static DataWord of(byte[] data) { + if (data == null || data.length == 0) { + return DataWord.ZERO; + } + + int leadingZeroBits = numberOfLeadingZeros(data); + int valueBits = 8 * data.length - leadingZeroBits; + if (valueBits <= 8) { + if (data[data.length - 1] == 0) return DataWord.ZERO; + if (data[data.length - 1] == 1) return DataWord.ONE; + } + + if (data.length == 32) + return new DataWord(Arrays.copyOf(data, data.length)); + else if (data.length <= 32) { + byte[] bytes = new byte[32]; + System.arraycopy(data, 0, bytes, 32 - data.length, data.length); + return new DataWord(bytes); + } else { + throw new RuntimeException(String.format("Data word can't exceed 32 bytes: 0x%s", Hex.toHexString(data))); + } + } + + public static DataWord of(ByteArrayWrapper wrappedData) { + return of(wrappedData.getData()); + } + + @JsonCreator + public static DataWord of(String data) { + return of(Hex.decode(data)); + } + + public static DataWord of(byte num) { + byte[] bb = new byte[32]; + bb[31] = num; + return new DataWord(bb); + + } + + public static DataWord of(int num) { + return of(ByteUtil.intToBytes(num)); + } + + public static DataWord of(long num) { + return of(ByteUtil.longToBytes(num)); + } + + + public byte[] getData() { + return Arrays.copyOf(data, data.length); + } + + + private byte[] copyData() { + return Arrays.copyOf(data, data.length); + } + + public byte[] getNoLeadZeroesData() { + return ByteUtil.stripLeadingZeroes(copyData()); + } + + public byte[] getLast20Bytes() { + return Arrays.copyOfRange(data, 12, data.length); + } + + public BigInteger value() { + return new BigInteger(1, data); + } + + + public int intValue() { + int intVal = 0; + + for (byte aData : data) { + intVal = (intVal << 8) + (aData & 0xff); + } + + return intVal; + } + + + public int intValueSafe() { + int bytesOccupied = bytesOccupied(); + int intValue = intValue(); + if (bytesOccupied > 4 || intValue < 0) return Integer.MAX_VALUE; + return intValue; + } + + + public long longValue() { + + long longVal = 0; + for (byte aData : data) { + longVal = (longVal << 8) + (aData & 0xff); + } + + return longVal; + } + + + public long longValueSafe() { + int bytesOccupied = bytesOccupied(); + long longValue = longValue(); + if (bytesOccupied > 8 || longValue < 0) return Long.MAX_VALUE; + return longValue; + } + + public BigInteger sValue() { + return new BigInteger(data); + } + + public String bigIntValue() { + return new BigInteger(data).toString(); + } + + public boolean isZero() { + if (this == ZERO) return true; + return this.compareTo(ZERO) == 0; + } + + + + + public boolean isNegative() { + int result = data[0] & 0x80; + return result == 0x80; + } + + public DataWord and(DataWord word) { + byte[] newData = this.copyData(); + for (int i = 0; i < this.data.length; ++i) { + newData[i] &= word.data[i]; + } + return new DataWord(newData); + } + + public DataWord or(DataWord word) { + byte[] newData = this.copyData(); + for (int i = 0; i < this.data.length; ++i) { + newData[i] |= word.data[i]; + } + return new DataWord(newData); + } + + public DataWord xor(DataWord word) { + byte[] newData = this.copyData(); + for (int i = 0; i < this.data.length; ++i) { + newData[i] ^= word.data[i]; + } + return new DataWord(newData); + } + + public DataWord negate() { + if (this.isZero()) return ZERO; + return bnot().add(DataWord.ONE); + } + + public DataWord bnot() { + if (this.isZero()) { + return new DataWord(ByteUtil.copyToArray(MAX_VALUE)); + } + return new DataWord(ByteUtil.copyToArray(MAX_VALUE.subtract(this.value()))); + } + + + + public DataWord add(DataWord word) { + byte[] newData = new byte[32]; + for (int i = 31, overflow = 0; i >= 0; i--) { + int v = (this.data[i] & 0xff) + (word.data[i] & 0xff) + overflow; + newData[i] = (byte) v; + overflow = v >>> 8; + } + return new DataWord(newData); + } + + + public DataWord add2(DataWord word) { + BigInteger result = value().add(word.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + + + public DataWord mul(DataWord word) { + BigInteger result = value().multiply(word.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord div(DataWord word) { + + if (word.isZero()) { + return ZERO; + } + + BigInteger result = value().divide(word.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord sDiv(DataWord word) { + + if (word.isZero()) { + return ZERO; + } + + BigInteger result = sValue().divide(word.sValue()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord sub(DataWord word) { + BigInteger result = value().subtract(word.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord exp(DataWord word) { + BigInteger newData = value().modPow(word.value(), _2_256); + return new DataWord(ByteUtil.copyToArray(newData)); + } + + + public DataWord mod(DataWord word) { + + if (word.isZero()) { + return ZERO; + } + + BigInteger result = value().mod(word.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + public DataWord sMod(DataWord word) { + + if (word.isZero()) { + return ZERO; + } + + BigInteger result = sValue().abs().mod(word.sValue().abs()); + result = (sValue().signum() == -1) ? result.negate() : result; + + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + public DataWord addmod(DataWord word1, DataWord word2) { + if (word2.isZero()) { + return ZERO; + } + + BigInteger result = value().add(word1.value()).mod(word2.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + public DataWord mulmod(DataWord word1, DataWord word2) { + + if (this.isZero() || word1.isZero() || word2.isZero()) { + return ZERO; + } + + BigInteger result = value().multiply(word1.value()).mod(word2.value()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord shiftLeft(DataWord arg) { + if (arg.value().compareTo(BigInteger.valueOf(MAX_POW)) >= 0) { + return DataWord.ZERO; + } + + BigInteger result = value().shiftLeft(arg.intValueSafe()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord shiftRight(DataWord arg) { + if (arg.value().compareTo(BigInteger.valueOf(MAX_POW)) >= 0) { + return DataWord.ZERO; + } + + BigInteger result = value().shiftRight(arg.intValueSafe()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + + public DataWord shiftRightSigned(DataWord arg) { + if (arg.value().compareTo(BigInteger.valueOf(MAX_POW)) >= 0) { + if (this.isNegative()) { + return DataWord.ONE.negate(); + } else { + return DataWord.ZERO; + } + } + + BigInteger result = sValue().shiftRight(arg.intValueSafe()); + return new DataWord(ByteUtil.copyToArray(result.and(MAX_VALUE))); + } + + @JsonValue + @Override + public String toString() { + return Hex.toHexString(data); + } + + public String toPrefixString() { + + byte[] pref = getNoLeadZeroesData(); + if (pref.length == 0) return ""; + + if (pref.length < 7) + return Hex.toHexString(pref); + + return Hex.toHexString(pref).substring(0, 6); + } + + public String shortHex() { + String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(); + return "0x" + hexValue.replaceFirst("^0+(?!$)", ""); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DataWord that = (DataWord) o; + + return Arrays.equals(this.data, that.data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + + @Override + public int compareTo(DataWord o) { + if (o == null) return -1; + int result = ByteComparator.compareTo( + data, 0, data.length, + o.data, 0, o.data.length); + + return (int) Math.signum(result); + } + + public DataWord signExtend(byte k) { + if (0 > k || k > 31) + throw new IndexOutOfBoundsException(); + byte mask = this.sValue().testBit((k * 8) + 7) ? (byte) 0xff : 0; + byte[] newData = this.copyData(); + for (int i = 31; i > k; i--) { + newData[31 - i] = mask; + } + return new DataWord(newData); + } + + public int bytesOccupied() { + int firstNonZero = ByteUtil.firstNonZeroByte(data); + if (firstNonZero == -1) return 0; + return 31 - firstNonZero + 1; + } + + public boolean isHex(String hex) { + return Hex.toHexString(data).equals(hex); + } + + public String asString() { + return new String(getNoLeadZeroesData()); + } +} diff --git a/common/src/test/java/org/platon/common/BasicPbCodecTest.java b/common/src/test/java/org/platon/common/BasicPbCodecTest.java new file mode 100644 index 0000000..23f41d8 --- /dev/null +++ b/common/src/test/java/org/platon/common/BasicPbCodecTest.java @@ -0,0 +1,57 @@ +package org.platon.common; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.junit.Assert; +import org.junit.Test; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.RandomUtils; + +import java.util.ArrayList; +import java.util.List; + +public class BasicPbCodecTest { + + private final byte[] bytes0 = "test0".getBytes(); + private final byte[] bytes1 = "test1".getBytes(); + private final byte[] bytes2 = "test2".getBytes(); + private final int intData = RandomUtils.randomInt(100); + private final long longData = RandomUtils.randomInt(1000000); + private final String stringData = "platon test"; + + @Test + public void encodeBytesList() throws InvalidProtocolBufferException { + byte[] result = BasicPbCodec.encodeBytesList(bytes0,bytes1,bytes2); + List bytesList = new ArrayList<>(); + bytesList.add(new ByteArrayWrapper(bytes0)); + bytesList.add(new ByteArrayWrapper(bytes1)); + bytesList.add(new ByteArrayWrapper(bytes2)); + List decodeList = BasicPbCodec.decodeBytesList(result); + for(int i=0;i entry : config.entrySet()) { + System.out.println("~> Name: " + entry.getKey()); + System.out.println("~> Value: " + entry.getValue()); + } + + + List list = config.getObjectList("peer.active.list"); + for (ConfigObject configObject : list) { + if (configObject.get("ip") != null) { + System.out.println("ip: " + configObject.toConfig().getString("ip")); + } + if (configObject.get("port") != null) { + int port = configObject.toConfig().getInt("port"); + System.out.println("port: " + port); + } + if (configObject.get("public-key") != null) { + String publicKey = configObject.toConfig().getString("public-key"); + System.out.println("public-key: " + publicKey); + } + } + } + + @Test + public void test02() { + + + System.setProperty("onekey", "Hello Key."); + + Config config = ConfigFactory.load("test-platon.conf"); + String string = config.getString("core.keystore.dir"); + Assert.assertNotNull(string); + + + Config overrides = ConfigFactory.parseString("onekey='Hello updated key.', peer.active=[{url=sdfsfd}]"); + Config merged = overrides.withFallback(config); + + System.out.println("onekey : " + merged.getString("onekey")); + Assert.assertNotEquals("Hello Key.", merged.getString("onekey")); + } + +} diff --git a/common/src/test/java/org/platon/common/utils/ByteComparatorTest.java b/common/src/test/java/org/platon/common/utils/ByteComparatorTest.java new file mode 100644 index 0000000..91cc11a --- /dev/null +++ b/common/src/test/java/org/platon/common/utils/ByteComparatorTest.java @@ -0,0 +1,27 @@ +package org.platon.common.utils; + +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; + +@Ignore +public class ByteComparatorTest { + + @Test + public void test01(){ + byte[] arr01 = new byte[]{1, 2, 3, 4}; + byte[] arr02 = new byte[]{1, 2, 3, 4}; + boolean res = ByteComparator.equals(arr01, arr02); + assertEquals(true, res); + } + + @Test + public void test02(){ + byte[] arr01 = new byte[]{1, 2, 3, 4}; + byte[] arr02 = new byte[]{1, 2, 3, 5}; + boolean res = ByteComparator.equals(arr01, arr02); + assertEquals(false, res); + } + +} \ No newline at end of file diff --git a/common/src/test/resources/test-platon.conf b/common/src/test/resources/test-platon.conf new file mode 100644 index 0000000..1bdfb99 --- /dev/null +++ b/common/src/test/resources/test-platon.conf @@ -0,0 +1,145 @@ +node = { + ip=192.168.7.109 +} + +peer = { + listen.port=12345 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + private-key=a392604efc2fad9c0b3da43b5f698a2e3f270f170d859912be0d54742275c5f6 + #in seconds + create.session.timeout = 10 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 12 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 30 + + active.list = + [ + { + ip=192.168.7.113 + port=11001 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + } + ] +} + +grpc = { + listen.port = 11001 +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} + + +core = { + + # keystore 默认存储路径 + # 默认路径:${user.dir}/{keystore.dir} + keystore.dir = keystore + + # 创世区块配置 + genesis = { + # 搜索路径:`resources/genesis` + # + resourcePath = genesis.json + + # 全路径 + # 优先级高于resourcePath + # filePath = /some/path/frontier.json + } + + database = { + # 数据存储物理路径(可为相对路径或全路径) + dir = database + + # 只支持leveldb或rocksdb + source = rocksdb + + # 版本不匹配的处理方式 : + # * EXIT - (默认)打印错误信息,并通过抛出异常退出程序 + # * RESET - 清除数据文件,程序继续执行 + # * IGNORE - 忽略存在的问题 + incompatibleDatabaseBehavior = EXIT + } + + mine = { + # 是否开启挖矿(true/false) + start = true + + # 出块时长(单位:毫秒) + mineBlockTime = 1000 + + # 旷工账号 + coinbase = "0000000000000000000000000000000000000000" + + # 最小能量price + minEnergonPrice = 15000000000 # 15Gwei + } + + validator = { + timeliness.ancestorBlockCache.size = 50 + timeliness.pendingBlockCache.size = 50 + timeliness.pendingTxCache.size = 5000 + } + + chain.only = false + block.record = false + + # enable/disable 内部交易记录 + record.internal.transactions.data = true + + dump = { + + full = false + dir = dmp + + # This defines the vmtrace dump + # to the console and the style + # -1 for no block trace + # styles: [pretty/standard+] (default: standard+) + block = -1 + + style = pretty + + # clean the dump dir each start + clean.on.restart = true + } + + vm = { + # + structured.trace = true + } +} + + + diff --git a/config/platon.conf b/config/platon.conf new file mode 100644 index 0000000..c16da33 --- /dev/null +++ b/config/platon.conf @@ -0,0 +1,116 @@ +node = { + host=192.168.7.109 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + private-key=a392604efc2fad9c0b3da43b5f698a2e3f270f170d859912be0d54742275c5f6 +} + +consensus = { + node.list = + [ + { + host=192.168.7.113 + port=11001 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + } + ] +} + +grpc = { + host=192.168.7.109 + listen.port = 11001 +} + +peer = { + listen.port=12345 + #in seconds + create.session.timeout = 10 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 12 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 30 + + active.list = + [ + { + host=192.168.7.113 + port=11001 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + } + ] +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} + +core = { + keystore.dir = keystore + genesis = { + resourcePath = genesis.json + # filePath = /some/path/frontier.json + } + + database = { + dir = database + source = rocksdb + incompatibleDatabaseBehavior = EXIT + } + + mine = { + start = true + mineBlockTime = 1000 + coinbase = "0000000000000000000000000000000000000000" + minEnergonPrice = 15000000000 + } + validator = { + timeliness.ancestorBlockCache.size = 50 + timeliness.pendingBlockCache.size = 50 + timeliness.pendingTxCache.size = 5000 + } + chain.only = false + block.record = false + record.internal.transactions.data = true + dump = { + + full = false + dir = dmp + block = -1 + style = pretty + clean.on.restart = true + } + vm = { + structured.trace = true + } +} + + + diff --git a/consensus/build.gradle b/consensus/build.gradle new file mode 100644 index 0000000..a8e28f7 --- /dev/null +++ b/consensus/build.gradle @@ -0,0 +1,9 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..140ee6d --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,26 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +ext { + junitVersion = '4.11' +} + +dependencies { + compile project(':slice') + compile project(':crypto') + compile project(':storage') + compile project(':p2p') + compile "org.apache.commons:commons-lang3:3.4" + compile "org.apache.commons:commons-collections4:4.0" + compile "com.google.protobuf:protobuf-java:3.5.1" + compile "org.bouncycastle:bcprov-jdk15on:1.59" + compile "org.projectlombok:lombok:1.16.20" + compile "com.fasterxml.jackson.core:jackson-databind:2.5.1" + compile "org.codehaus.jackson:jackson-mapper-asl:1.9.13" + + + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/core/src/main/java/org/platon/Start.java b/core/src/main/java/org/platon/Start.java new file mode 100644 index 0000000..2841dc9 --- /dev/null +++ b/core/src/main/java/org/platon/Start.java @@ -0,0 +1,17 @@ +package org.platon; + +import org.platon.core.facade.PlatonFactory; +import org.platon.core.facade.PlatonImpl; + +import java.io.IOException; +import java.net.URISyntaxException; + +public class Start { + + public static void main(String args[]) throws IOException, URISyntaxException { + + PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(); + + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/Account.java b/core/src/main/java/org/platon/core/Account.java new file mode 100644 index 0000000..1a60afe --- /dev/null +++ b/core/src/main/java/org/platon/core/Account.java @@ -0,0 +1,169 @@ +package org.platon.core; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteComparator; +import org.platon.core.enums.AccountTypeEnum; +import org.platon.core.proto.AccountProto; +import org.platon.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Account + * + * @author yanze + * @desc base account model + * @create 2018-07-26 17:02 + **/ +public class Account { + + private static final Logger logger = LoggerFactory.getLogger(Account.class); + + private byte[] protoBuf; + + private BigInteger balance = BigInteger.ZERO; + + private byte[] storageRoot; + + private byte[] binHash; + + private byte[] bin; + + private byte[] permissionRoot; + + private boolean dirty; + + private Map storageMap; + + public Account(byte[] protoBuf){ + this.protoBuf = protoBuf; + //analyze account proto + try { + AccountProto accountProto = AccountProto.parseFrom(protoBuf); + this.balance = new BigInteger(accountProto.getBalance().toByteArray()); + this.binHash = accountProto.getBinHash().toByteArray(); + this.storageRoot = accountProto.getExtraRoot().toByteArray(); + this.permissionRoot = accountProto.getPermissionRoot().toByteArray(); + this.dirty = false; + this.storageMap = new LinkedHashMap<>(); + } catch (InvalidProtocolBufferException e) { + this.protoBuf = null; + logger.error("init account error",e); + } + } + + public Account(BigInteger balance, byte[] storageRoot, byte[] binHash, byte[] permissionRoot) { + this.balance = balance; + this.storageRoot = storageRoot; + this.binHash = binHash; + this.permissionRoot = permissionRoot; + this.storageMap = new LinkedHashMap<>(); + this.dirty = balance.compareTo(BigInteger.ZERO) > 0; + } + + public byte[] getEncoded(){ + if(dirty){ + //proto encode + AccountProto.Builder accountProtoBuilder = AccountProto.newBuilder(); + accountProtoBuilder.setBalance(ByteString.copyFrom(this.balance.toByteArray())); + accountProtoBuilder.setBinHash(ByteString.copyFrom(this.binHash)); + accountProtoBuilder.setExtraRoot(ByteString.copyFrom(this.storageRoot)); + accountProtoBuilder.setPermissionRoot(ByteString.copyFrom(this.permissionRoot)); + AccountProto accountProto = accountProtoBuilder.build(); + this.protoBuf = accountProto.toByteArray(); + } + return protoBuf; + } + + public boolean addBalance(BigInteger value){ + BigInteger result = balance.add(value); + if (result.compareTo(BigInteger.ZERO) < 0) { + //result balance < 0 + return false; + } + balance = result; + this.dirty = true; + return true; + } + + public AccountTypeEnum getAccountType(){ + if(Arrays.equals(HashUtil.EMPTY_HASH,this.binHash)){ + return AccountTypeEnum.ACCOUNT_TYPE_EXTERNAL; + }else{ + return AccountTypeEnum.ACCOUNT_TYPE_CONTRACT; + } + } + + public boolean isEmpty(){ + return Arrays.equals(HashUtil.EMPTY_HASH,storageRoot) + &&Arrays.equals(HashUtil.EMPTY_HASH,binHash) + &&Arrays.equals(HashUtil.EMPTY_HASH,permissionRoot) + &&BigInteger.ZERO.equals(balance); + } + + public void untouch(){ + this.dirty = false; + } + + public boolean isContractExist() { + return !ByteComparator.equals(binHash, HashUtil.EMPTY_HASH); + } + + public byte[] getBin() { + return bin; + } + + public void setBin(byte[] bin) { + this.binHash = HashUtil.sha3(bin); + this.bin = bin; + } + + public void setStorage(byte[] key, byte[] value){ + storageMap.put(new ByteArrayWrapper(key),value); + } + + public byte[] getStorage(byte[] key){ + return storageMap.get(new ByteArrayWrapper(key)); + } + + public Map getStorageMap() { + return storageMap; + } + + public void setPermissionRoot(byte[] permissionRoot) { + this.permissionRoot = permissionRoot; + } + + public BigInteger getBalance() { + return balance; + } + + public byte[] getStorageRoot() { + return storageRoot; + } + + public void setStorageRoot(byte[] storageRoot) { + this.storageRoot = storageRoot; + } + + public byte[] getBinHash() { + return binHash; + } + + public byte[] getPermissionRoot() { + return permissionRoot; + } + + public boolean isDirty() { + return dirty; + } + +} + diff --git a/core/src/main/java/org/platon/core/BlockIdentifier.java b/core/src/main/java/org/platon/core/BlockIdentifier.java new file mode 100644 index 0000000..8829a36 --- /dev/null +++ b/core/src/main/java/org/platon/core/BlockIdentifier.java @@ -0,0 +1,45 @@ +package org.platon.core; + +import com.google.protobuf.ByteString; +import org.platon.common.utils.Numeric; +import org.platon.core.proto.BlockIdentifierMessage; + +public class BlockIdentifier { + + private byte[] hash; + + private long number; + + public BlockIdentifier(BlockIdentifierMessage pbMessage) { + this.hash = pbMessage.getHash().toByteArray(); + this.number = pbMessage.getNumber(); + } + + public BlockIdentifier(byte[] hash, long number) { + this.hash = hash; + this.number = number; + } + + public byte[] getHash() { + return hash; + } + + public long getNumber() { + return number; + } + + public byte[] getEncoded() { + BlockIdentifierMessage.Builder builder = BlockIdentifierMessage.newBuilder(); + builder.setHash(ByteString.copyFrom(hash)); + builder.setNumber(number); + return builder.build().toByteArray(); + } + + @Override + public String toString() { + return "BlockIdentifier {" + + "hash=" + Numeric.toHexString(hash) + + ", number = " + number + + '}'; + } +} diff --git a/core/src/main/java/org/platon/core/BlockSummary.java b/core/src/main/java/org/platon/core/BlockSummary.java new file mode 100644 index 0000000..46e40ca --- /dev/null +++ b/core/src/main/java/org/platon/core/BlockSummary.java @@ -0,0 +1,52 @@ +package org.platon.core; + +import org.platon.core.block.Block; +import org.platon.core.transaction.TransactionReceipt; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +public class BlockSummary { + + private final Block block; + private final Map rewards; + private final List receipts; + private final List txExecutionSummaries; + private BigInteger totalDifficulty = BigInteger.ZERO; + + public BlockSummary(Block block, Map bigIntegerHashMap, + List transactionReceipts, + List txExecutionSummaries) { + + this.receipts = transactionReceipts; + this.rewards = bigIntegerHashMap; + this.block = block; + this.txExecutionSummaries = txExecutionSummaries; + } + + public List getReceipts() { + return receipts; + } + + public List getSummaries() { + return txExecutionSummaries; + } + + + public Map getRewards() { + return rewards; + } + + public void setTotalDifficulty(BigInteger totalDifficulty) { + this.totalDifficulty = totalDifficulty; + } + + public BigInteger getTotalDifficulty() { + return totalDifficulty; + } + + public boolean betterThan(BigInteger oldTotDifficulty) { + return getTotalDifficulty().compareTo(oldTotDifficulty) > 0; + } +} diff --git a/core/src/main/java/org/platon/core/Blockchain.java b/core/src/main/java/org/platon/core/Blockchain.java new file mode 100644 index 0000000..58932d6 --- /dev/null +++ b/core/src/main/java/org/platon/core/Blockchain.java @@ -0,0 +1,64 @@ +package org.platon.core; + +import org.platon.core.block.BlockHeader; +import org.platon.core.block.Block; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; + +import java.math.BigInteger; +import java.util.Iterator; +import java.util.List; + +public interface Blockchain { + + ImportResult tryToConnect(Block block); + + long getSize(); + + BlockSummary add(Block block); + + void storeBlock(Block block, List receipts); + + Block getBlockByNumber(long blockNumber); + + void setBestBlock(Block block); + + Block getBestBlock(); + + boolean hasParentOnTheChain(Block block); + + void close(); + + // ==================== for difficulty ================ + + void updateTotalDifficulty(Block block); + + BigInteger getTotalDifficulty(); + + void setTotalDifficulty(BigInteger totalDifficulty); + + byte[] getBestBlockHash(); + + List getListOfHashesStartFrom(byte[] hash, int qty); + + List getListOfHashesStartFromBlock(long blockNumber, int qty); + + TransactionInfo getTransactionInfo(byte[] hash); + + Block getBlockByHash(byte[] hash); + + void setExitOn(long exitOn); + + byte[] getMinerCoinbase(); + + boolean isBlockExist(byte[] hash); + + BlockStoreIfc getBlockStore(); + + Block createNewBlock(Block parent, List transactions, List uncles); + + Iterator getIteratorOfHeadersStartFrom(BlockIdentifier identifier, int skip, int limit, boolean reverse) ; + + +} diff --git a/core/src/main/java/org/platon/core/BlockchainImpl.java b/core/src/main/java/org/platon/core/BlockchainImpl.java new file mode 100644 index 0000000..057ac76 --- /dev/null +++ b/core/src/main/java/org/platon/core/BlockchainImpl.java @@ -0,0 +1,962 @@ +package org.platon.core; + +import org.apache.commons.lang3.tuple.Pair; +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteComparator; +import org.platon.common.utils.Numeric; +import org.platon.core.block.BlockHeader; +import org.platon.core.config.CommonConfig; +import org.platon.core.config.CoreConfig; +import org.platon.core.listener.PlatonListener; +import org.platon.core.listener.PlatonListenerAdapter; +import org.platon.core.proto.IntMessage; +import org.platon.core.block.Block; +import org.platon.core.datasource.TransactionStore; +import org.platon.core.db.*; +import org.platon.storage.trie.Trie; +import org.platon.storage.trie.TrieImpl; +import org.platon.core.transaction.Bloom; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.inmemory.HashMapDB; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.math.BigInteger; +import java.util.*; + +import static java.lang.Runtime.getRuntime; +import static java.math.BigInteger.ZERO; +import static java.util.Collections.emptyList; +import static org.platon.core.Denomination.SZABO; + +@Component +public class BlockchainImpl implements Blockchain { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + private static final Logger stateLogger = LoggerFactory.getLogger(AppenderName.APPENDER_STATE); + + + private static final long INITIAL_MIN_GAS_PRICE = 10 * SZABO.longValue(); + private static final int MAGIC_REWARD_OFFSET = 8; + + @Autowired + @Qualifier("defaultRepository") + private Repository repository; + + @Autowired + protected BlockStoreIfc blockStore; + + private HeaderStore headerStore = null; + + @Autowired + private TransactionStore transactionStore; + + @Autowired + private PendingStateIfc pendingState; + + + private Block bestBlock; + + + private BigInteger totalDifficulty = ZERO; + + @Autowired + private PlatonListener listener; + + @Autowired + EventDispatchWorker eventDispatchWorker; + + @Autowired + CommonConfig commonConfig; + + @Autowired + StateSource stateDataSource; + + @Autowired + DbFlushManager dbFlushManager; + + CoreConfig config = CoreConfig.getInstance(); + + long exitOn = Long.MAX_VALUE; + + public boolean byTest = false; + private boolean fork = false; + + private byte[] minerCoinbase; + private byte[] minerExtraData; + private int UNCLE_LIST_LIMIT; + private int UNCLE_GENERATION_LIMIT; + + private Stack stateStack = new Stack<>(); + + + public BlockchainImpl() { + } + + @Autowired + public BlockchainImpl(final CoreConfig config) { + this.config = config; + initConst(config); + } + + + public BlockchainImpl(final BlockStoreIfc blockStore, final Repository repository) { + this.blockStore = blockStore; + this.repository = repository; + this.listener = new PlatonListenerAdapter(); + + this.transactionStore = new TransactionStore(new HashMapDB()); + this.eventDispatchWorker = eventDispatchWorker.getDefault(); + + initConst(CoreConfig.getInstance()); + } + + public BlockchainImpl withTransactionStore(TransactionStore transactionStore) { + this.transactionStore = transactionStore; + return this; + } + + public BlockchainImpl withPlatonListener(PlatonListener listener) { + this.listener = listener; + return this; + } + + + private void initConst(CoreConfig config) { + minerCoinbase = config.getMinerCoinbase(); + minerExtraData = new byte[]{0}; + UNCLE_LIST_LIMIT = 2; + UNCLE_GENERATION_LIMIT = 7; + } + + @Override + public byte[] getBestBlockHash() { + return getBestBlock().getBlockHeader().getHash(); + } + + @Override + public long getSize() { + return bestBlock.getBlockHeader().getNumber() + 1; + } + + @Override + public Block getBlockByNumber(long blockNr) { + return blockStore.getChainBlockByNumber(blockNr); + } + + @Override + public TransactionInfo getTransactionInfo(byte[] hash) { + + List infos = transactionStore.get(hash); + + if (infos == null || infos.isEmpty()) + return null; + + TransactionInfo txInfo = null; + if (infos.size() == 1) { + txInfo = infos.get(0); + } else { + + for (TransactionInfo info : infos) { + Block block = blockStore.getBlockByHash(info.getBlockHash()); + Block mainBlock = blockStore.getChainBlockByNumber(block.getBlockHeader().getNumber()); + if (ByteComparator.equals(info.getBlockHash(), mainBlock.getBlockHeader().getHash())) { + txInfo = info; + break; + } + } + } + if (txInfo == null) { + logger.warn("Can't find block from main chain for transaction " + Numeric.toHexString(hash)); + return null; + } + + Transaction tx = this.getBlockByHash(txInfo.getBlockHash()).getTransactions().get(txInfo.getIndex()); + txInfo.setTransaction(tx); + + return txInfo; + } + + @Override + public Block getBlockByHash(byte[] hash) { + return blockStore.getBlockByHash(hash); + } + + @Override + public synchronized List getListOfHashesStartFrom(byte[] hash, int qty) { + return blockStore.getListHashesEndWith(hash, qty); + } + + @Override + public synchronized List getListOfHashesStartFromBlock(long blockNumber, int qty) { + long bestNumber = bestBlock.getBlockHeader().getNumber(); + + if (blockNumber > bestNumber) { + return emptyList(); + } + + if (blockNumber + qty - 1 > bestNumber) { + qty = (int) (bestNumber - blockNumber + 1); + } + + long endNumber = blockNumber + qty - 1; + + Block block = getBlockByNumber(endNumber); + + List hashes = blockStore.getListHashesEndWith(block.getBlockHeader().getHash(), qty); + + + Collections.reverse(hashes); + + return hashes; + } + + + public static byte[] calcTxTrie(List transactions) { + + + Trie txsState = new TrieImpl(); + + if (transactions == null || transactions.isEmpty()) { + return HashUtil.EMPTY_HASH; + } + + for (int i = 0; i < transactions.size(); i++) { + Transaction tx = transactions.get(i); + + + txsState.put(IntMessage.newBuilder().setData(i).build().toByteArray(), tx.getEncoded()); + } + return txsState.getRootHash(); + } + + public Repository getRepository() { + return repository; + } + + + public Repository getRepositorySnapshot() { + + return repository.getSnapshotTo(blockStore.getBestBlock().getBlockHeader().getStateRoot()); + } + + @Override + public BlockStoreIfc getBlockStore() { + return blockStore; + } + + private State pushState(byte[] bestBlockHash) { + State push = stateStack.push(new State()); + this.bestBlock = blockStore.getBlockByHash(bestBlockHash); + totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlockHash); + this.repository = this.repository.getSnapshotTo(this.bestBlock.getBlockHeader().getStateRoot()); + return push; + } + + private void popState() { + State state = stateStack.pop(); + this.repository = repository.getSnapshotTo(state.root); + this.bestBlock = state.savedBest; + this.totalDifficulty = state.savedTD; + } + + public void dropState() { + stateStack.pop(); + } + + private synchronized BlockSummary tryConnectAndFork(final Block block) { + + State savedState = pushState(block.getBlockHeader().getParentHash()); + this.fork = true; + + final BlockSummary summary; + Repository repo; + try { + + + Block parentBlock = getBlockByHash(block.getBlockHeader().getParentHash()); + repo = repository.getSnapshotTo(parentBlock.getBlockHeader().getStateRoot()); + summary = add(repo, block); + if (summary == null) { + return null; + } + } catch (Throwable th) { + logger.error("Unexpected error: ", th); + return null; + } finally { + this.fork = false; + } + + if (summary.betterThan(savedState.savedTD)) { + + logger.info("Rebranching: {} ~> {}", savedState.savedBest.getShortHash(), block.getShortHash()); + + + + + blockStore.reBranch(block); + + + this.repository = repo; + + + dropState(); + } else { + + popState(); + } + + return summary; + } + + @Override + public synchronized ImportResult tryToConnect(final Block block) { + + + + if (logger.isDebugEnabled()) { + logger.debug("Try connect block hash: {}, number: {}", + Numeric.toHexString(block.getBlockHeader().getHash()).substring(0, 6), + block.getBlockHeader().getNumber()); + } + + + if (blockStore.getMaxNumber() >= block.getBlockHeader().getNumber() + && blockStore.isBlockExist(block.getBlockHeader().getHash())) { + + if (logger.isDebugEnabled()) { + logger.debug("~> Block already exist hash: {}, number: {}", + Numeric.toHexString(block.getBlockHeader().getHash()).substring(0, 6), + block.getBlockHeader().getNumber()); + } + return ImportResult.EXIST; + } + + final ImportResult ret; + + + + final BlockSummary summary; + + + + if (bestBlock.isParentOf(block)) { + recordBlock(block); + summary = add(repository, block); + + ret = summary == null ? ImportResult.INVALID_BLOCK : ImportResult.IMPORTED_BEST; + } else { + + + + + + if (blockStore.isBlockExist(block.getBlockHeader().getParentHash())) { + + + + + + BigInteger oldTotalDiff = getTotalDifficulty(); + + recordBlock(block); + summary = tryConnectAndFork(block); + + ret = summary == null ? ImportResult.INVALID_BLOCK : + (summary.betterThan(oldTotalDiff) ? ImportResult.IMPORTED_BEST : ImportResult.IMPORTED_NOT_BEST); + } else { + summary = null; + ret = ImportResult.NO_PARENT; + } + + } + + if (ret.isSuccessful()) { + listener.onBlock(summary); + listener.trace(String.format("Block chain size: [ %d ]", this.getSize())); + + if (ret == ImportResult.IMPORTED_BEST) { + eventDispatchWorker.invokeLater(() -> pendingState.processBest(block, summary.getReceipts())); + } + } + + return ret; + } + + + public synchronized Block createNewBlock(Block parent, List txs, List uncles) { + if (parent == null) { + return null; + } + long time = System.currentTimeMillis() / 1000; + if (parent.getBlockHeader().getTimestamp() >= time) { + time = parent.getBlockHeader().getTimestamp() + 1; + } + return createNewBlock(parent, txs, uncles, time); + } + + public synchronized Block createNewBlock(Block parent, List txs, List uncles, long time) { + + final long blockNumber = parent.getBlockHeader().getNumber() + 1; + + final byte[] extraData = new byte[0]; + + + Block block = new Block( + time, + new byte[32], + new byte[32], + parent.getBlockHeader().getHash(), + new byte[32], + blockNumber, + BigInteger.ZERO, + parent.getBlockHeader().getEnergonCeiling(), + BigInteger.ZERO, + extraData + ); + block.setTransactions(txs); + block.getBlockHeader().setRoots( + new byte[]{0}, + new byte[32], + new byte[32], + calcTxTrie(txs), + new byte[32], + new byte[32], + new byte[32] + ); + + Repository track = repository.getSnapshotTo(parent.getBlockHeader().getStateRoot()); + + BlockSummary summary = applyBlock(track, block); + + List receipts = summary.getReceipts(); + block.getBlockHeader().setStateRoot(track.getRoot()); + + Bloom logBloom = new Bloom(); + for (TransactionReceipt receipt : receipts) { + logBloom.or(receipt.getBloomFilter()); + } + block.getBlockHeader().setBloomLog(logBloom.getData()); + block.getBlockHeader().setEnergonUsed(receipts.size() > 0 ? BigInteger.valueOf(receipts.get(receipts.size() - 1).getCumulativeEnergonLong()) : BigInteger.ZERO); + block.getBlockHeader().setReceiptRoot(calcReceiptsTrie(receipts)); + + return block; + } + + @Override + public BlockSummary add(Block block) { + throw new RuntimeException("Not supported"); + } + + public synchronized BlockSummary add(Repository repo, final Block block) { + + BlockSummary summary = addImpl(repo, block); + if (summary == null) { + stateLogger.warn("Trying to reimport the block for debug..."); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + BlockSummary summary1 = addImpl(repo.getSnapshotTo(getBestBlock().getBlockHeader().getStateRoot()), block); + stateLogger.warn("Second import trial " + (summary1 == null ? "FAILED" : "OK")); + if (summary1 != null) { + if (!byTest) { + stateLogger.error("Inconsistent behavior, exiting..."); + System.exit(-1); + } else { + return summary1; + } + } + } + return summary; + } + + public synchronized BlockSummary addImpl(Repository repo, final Block block) { + + if (exitOn < block.getBlockHeader().getNumber()) { + System.out.print("Exiting after block.number: " + bestBlock.getBlockHeader().getNumber()); + dbFlushManager.flushSync(); + System.exit(-1); + } + byte[] origRoot = repo.getRoot(); + + if (block == null) { + return null; + } + + BlockSummary summary = processBlock(repo, block); + final List receipts = summary.getReceipts(); + + + if (!ByteComparator.equals(block.getBlockHeader().getReceiptRoot(), calcReceiptsTrie(receipts))) { + logger.warn("Block's given Receipt Hash doesn't match: {} != {}", Numeric.toHexString(block.getBlockHeader().getReceiptRoot()), Numeric.toHexString(calcReceiptsTrie(receipts))); + logger.warn("Calculated receipts: " + receipts); + repo.rollback(); + summary = null; + } + + if (!ByteComparator.equals(block.getBlockHeader().getBloomLog(), calcLogBloom(receipts))) { + logger.warn("Block's given logBloom Hash doesn't match: {} != {}", Numeric.toHexString(block.getBlockHeader().getBloomLog()), Numeric.toHexString(calcLogBloom(receipts))); + repo.rollback(); + summary = null; + } + + if (!ByteComparator.equals(block.getBlockHeader().getStateRoot(), repo.getRoot())) { + + stateLogger.warn("BLOCK: State conflict or received invalid block. block: {} worldstate {} mismatch", block.getBlockHeader().getNumber(), Numeric.toHexString(repo.getRoot())); + stateLogger.warn("Conflict block dump: {}", Numeric.toHexString(block.encode())); + + repository = repository.getSnapshotTo(origRoot); + + if (!byTest) { + + System.out.println("CONFLICT: BLOCK #" + block.getBlockHeader().getNumber() + ", dump: " + Numeric.toHexString(block.encode())); + System.exit(1); + } else { + summary = null; + } + } + + if (summary != null) { + repo.commit(); + updateTotalDifficulty(block); + + if (!byTest) { + dbFlushManager.commit(() -> { + storeBlock(block, receipts); + repository.commit(); + }); + } else { + storeBlock(block, receipts); + } + } + + return summary; + } + + private boolean needFlushByMemory(double maxMemoryPercents) { + return getRuntime().freeMemory() < (getRuntime().totalMemory() * (1 - maxMemoryPercents)); + } + + public static byte[] calcReceiptsTrie(List receipts) { + Trie receiptsTrie = new TrieImpl(); + + if (receipts == null || receipts.isEmpty()) + return HashUtil.EMPTY_HASH; + + for (int i = 0; i < receipts.size(); i++) { + receiptsTrie.put(new byte[]{(byte) ((i >> 24) & 0xFF), (byte) ((i >> 16) & 0xFF), (byte) ((i >> 8) & 0xFF), (byte) (i & 0xFF)}, receipts.get(i).getReceiptTrieEncoded()); + } + return receiptsTrie.getRootHash(); + } + + private byte[] calcLogBloom(List receipts) { + + Bloom retBloomFilter = new Bloom(); + + if (receipts == null || receipts.isEmpty()) + return retBloomFilter.getData(); + + for (TransactionReceipt receipt : receipts) { + retBloomFilter.or(receipt.getBloomFilter()); + } + + return retBloomFilter.getData(); + } + + public Block getParent(BlockHeader header) { + + return blockStore.getBlockByHash(header.getParentHash()); + } + + + public static Set getAncestors(BlockStoreIfc blockStore, Block testedBlock, int limitNum, boolean isParentBlock) { + Set ret = new HashSet<>(); + limitNum = (int) Math.max(0, testedBlock.getBlockHeader().getNumber() - limitNum); + Block it = testedBlock; + if (!isParentBlock) { + it = blockStore.getBlockByHash(it.getBlockHeader().getParentHash()); + } + while (it != null && it.getBlockHeader().getNumber() >= limitNum) { + ret.add(new ByteArrayWrapper(it.getBlockHeader().getHash())); + it = blockStore.getBlockByHash(it.getBlockHeader().getParentHash()); + } + return ret; + } + + private BlockSummary processBlock(Repository track, Block block) { + + if (!block.isGenesis() && !config.blockChainOnly()) { + return applyBlock(track, block); + } else { + return new BlockSummary(block, new HashMap(), new ArrayList(), new ArrayList()); + } + } + + + private BlockSummary applyBlock(Repository track, Block block) { + + if (logger.isDebugEnabled()) { + logger.debug("applyBlock: block: [{}] tx.list: [{}]", block.getBlockHeader().getNumber(), block.getTransactions().size()); + } + long saveTime = System.nanoTime(); + int i = 1; + long totalEnergonUsed = 0; + List receipts = new ArrayList<>(); + List summaries = new ArrayList<>(); + + for (Transaction tx : block.getTransactions()) { + stateLogger.debug("apply block: [{}] tx: [{}] ", block.getBlockHeader().getNumber(), i); + + Repository txTrack = track.startTracking(); + + + stateLogger.info("block: [{}] executed tx: [{}] \n state: [{}]", block.getBlockHeader().getNumber(), i, + Numeric.toHexString(track.getRoot())); + + + } + + Map rewards = new HashMap<>(); + + stateLogger.info("applied reward for block: [{}] \n state: [{}]", + block.getBlockHeader().getNumber(), + Numeric.toHexString(track.getRoot())); + + long totalTime = System.nanoTime() - saveTime; + if (logger.isDebugEnabled()) { + logger.debug("block: num: [{}] hash: [{}], executed after: [{}]nano", block.getBlockHeader().getNumber(), Numeric.toHexString(block.getBlockHeader().getHash()).substring(0, 6), totalTime); + } + + + return new BlockSummary(block, rewards, receipts, summaries); + } + + @Override + public synchronized void storeBlock(Block block, List receipts) { + + if (fork) + blockStore.saveBlock(block, totalDifficulty, false); + else + blockStore.saveBlock(block, totalDifficulty, true); + + for (int i = 0; i < receipts.size(); i++) { + transactionStore.put(new TransactionInfo(receipts.get(i), block.getBlockHeader().getHash(), i)); + } + + logger.debug("Block saved: number: {}, hash: {}, TD: {}", + block.getBlockHeader().getNumber(), block.getBlockHeader().getHash(), totalDifficulty); + + setBestBlock(block); + + if (logger.isDebugEnabled()) + logger.debug("block added to the blockChain: index: [{}]", block.getBlockHeader().getNumber()); + if (block.getBlockHeader().getNumber() % 100 == 0) + logger.info("*** Last block added [ #{} ]", block.getBlockHeader().getNumber()); + + } + + public boolean hasParentOnTheChain(Block block) { + return getParent(block.getBlockHeader()) != null; + } + + public TransactionStore getTransactionStore() { + return transactionStore; + } + + @Override + public void setBestBlock(Block block) { + bestBlock = block; + repository = repository.getSnapshotTo(block.getBlockHeader().getStateRoot()); + } + + @Override + public synchronized Block getBestBlock() { + + + return bestBlock; + } + + @Override + public synchronized void close() { + blockStore.close(); + } + + @Override + public BigInteger getTotalDifficulty() { + return totalDifficulty; + } + + @Override + public synchronized void updateTotalDifficulty(Block block) { + totalDifficulty = totalDifficulty.add(block.getTotalDifficulty()); + if (logger.isDebugEnabled()) { + logger.debug("TD: updated to {}", totalDifficulty); + } + } + + @Override + public void setTotalDifficulty(BigInteger totalDifficulty) { + this.totalDifficulty = totalDifficulty; + } + + private void recordBlock(Block block) { + + if (!config.isRecordBlocks()) return; + + String dumpDir = config.databaseDir() + "/" + config.dumpDir(); + + File dumpFile = new File(dumpDir + "/blocks-rec.dmp"); + FileWriter fw = null; + BufferedWriter bw = null; + + try { + + dumpFile.getParentFile().mkdirs(); + if (!dumpFile.exists()) dumpFile.createNewFile(); + + fw = new FileWriter(dumpFile.getAbsoluteFile(), true); + bw = new BufferedWriter(fw); + + if (bestBlock.getBlockHeader().getNumber() == 0) { + bw.write(Hex.toHexString(bestBlock.encode())); + bw.write("\n"); + } + + bw.write(Hex.toHexString(block.encode())); + bw.write("\n"); + + } catch (IOException e) { + logger.error(e.getMessage(), e); + } finally { + try { + if (bw != null) bw.close(); + if (fw != null) fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void updateBlockTotDifficulties(long startFrom) { + + while (true) { + synchronized (this) { + ((BlockStoreImpl) blockStore).updateTotDifficulties(startFrom); + + if (startFrom == bestBlock.getBlockHeader().getNumber()) { + totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlock.getBlockHeader().getHash()); + } + + if (startFrom == blockStore.getMaxNumber()) { + Block bestStoredBlock = bestBlock; + BigInteger maxTD = totalDifficulty; + + + for (long num = bestBlock.getBlockHeader().getNumber() + 1; num <= blockStore.getMaxNumber(); num++) { + List blocks = ((BlockStoreImpl) blockStore).getBlocksByNumber(num); + for (Block block : blocks) { + BigInteger td = blockStore.getTotalDifficultyForHash(block.getBlockHeader().getHash()); + if (maxTD.compareTo(td) < 0) { + maxTD = td; + bestStoredBlock = block; + } + } + } + + if (totalDifficulty.compareTo(maxTD) < 0) { + blockStore.reBranch(bestStoredBlock); + bestBlock = bestStoredBlock; + totalDifficulty = maxTD; + repository = repository.getSnapshotTo(bestBlock.getBlockHeader().getStateRoot()); + } + + break; + } + startFrom++; + } + } + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setExitOn(long exitOn) { + this.exitOn = exitOn; + } + + public void setMinerCoinbase(byte[] minerCoinbase) { + this.minerCoinbase = minerCoinbase; + } + + @Override + public byte[] getMinerCoinbase() { + return minerCoinbase; + } + + public void setMinerExtraData(byte[] minerExtraData) { + this.minerExtraData = minerExtraData; + } + + public boolean isBlockExist(byte[] hash) { + return blockStore.isBlockExist(hash); + } + + @Override + public Iterator getIteratorOfHeadersStartFrom(BlockIdentifier identifier, int skip, int limit, boolean reverse) { + + + BlockHeader startHeader; + if (identifier.getHash() != null) { + startHeader = findHeaderByHash(identifier.getHash()); + } else { + startHeader = findHeaderByNumber(identifier.getNumber()); + } + + + if (startHeader == null) { + return EmptyBlockHeadersIterator.INSTANCE; + } + + if (identifier.getHash() != null) { + BlockHeader mainChainHeader = findHeaderByNumber(startHeader.getNumber()); + if (!startHeader.equals(mainChainHeader)) return EmptyBlockHeadersIterator.INSTANCE; + } + + return new BlockHeadersIterator(startHeader, skip, limit, reverse); + } + + + private BlockHeader findHeaderByNumber(long number) { + Block block = blockStore.getChainBlockByNumber(number); + if (block == null) { + if (headerStore != null) { + return headerStore.getHeaderByNumber(number); + } else { + return null; + } + } else { + return block.getBlockHeader(); + } + } + + + private BlockHeader findHeaderByHash(byte[] hash) { + Block block = blockStore.getBlockByHash(hash); + if (block == null) { + if (headerStore != null) { + return headerStore.getHeaderByHash(hash); + } else { + return null; + } + } else { + return block.getBlockHeader(); + } + } + + static class EmptyBlockHeadersIterator implements Iterator { + + final static EmptyBlockHeadersIterator INSTANCE = new EmptyBlockHeadersIterator(); + + @Override + public boolean hasNext() { + return false; + } + + @Override + public BlockHeader next() { + throw new NoSuchElementException("Nothing left"); + } + } + + + class BlockHeadersIterator implements Iterator { + + private final BlockHeader startHeader; + private final int skip; + private final int limit; + private final boolean reverse; + private Integer position = 0; + private Pair cachedNext = null; + + BlockHeadersIterator(BlockHeader startHeader, int skip, int limit, boolean reverse) { + this.startHeader = startHeader; + this.skip = skip; + this.limit = limit; + this.reverse = reverse; + } + + @Override + public boolean hasNext() { + if (startHeader == null || position >= limit) { + return false; + } + + if (position == 0) { + + cachedNext = Pair.of(0, startHeader); + return true; + } else if (cachedNext.getLeft().equals(position)) { + + return true; + } else { + + BlockHeader prevHeader = cachedNext.getRight(); + long nextBlockNumber; + if (reverse) { + nextBlockNumber = prevHeader.getNumber() - 1 - skip; + } else { + nextBlockNumber = prevHeader.getNumber() + 1 + skip; + } + + BlockHeader nextHeader = null; + if (nextBlockNumber >= 0 && nextBlockNumber <= blockStore.getBestBlock().getBlockHeader().getNumber()) { + nextHeader = findHeaderByNumber(nextBlockNumber); + } + + if (nextHeader == null) { + return false; + } else { + cachedNext = Pair.of(position, nextHeader); + return true; + } + } + } + + @Override + public BlockHeader next() { + if (!hasNext()) { + throw new NoSuchElementException("Nothing left"); + } + + if (cachedNext == null || !cachedNext.getLeft().equals(position)) { + throw new ConcurrentModificationException("Concurrent modification"); + } + ++position; + + return cachedNext.getRight(); + } + } + + private class State { + byte[] root = repository.getRoot(); + Block savedBest = bestBlock; + BigInteger savedTD = totalDifficulty; + } + + public void setHeaderStore(HeaderStore headerStore) { + this.headerStore = headerStore; + } +} diff --git a/core/src/main/java/org/platon/core/Denomination.java b/core/src/main/java/org/platon/core/Denomination.java new file mode 100644 index 0000000..c323ca1 --- /dev/null +++ b/core/src/main/java/org/platon/core/Denomination.java @@ -0,0 +1,44 @@ +package org.platon.core; + +import java.math.BigInteger; + + +public enum Denomination { + + WEI(newBigInt(0)), + SZABO(newBigInt(12)), + FINNEY(newBigInt(15)), + ETHER(newBigInt(18)); + + private BigInteger amount; + + private Denomination(BigInteger value) { + this.amount = value; + } + + public BigInteger value() { + return amount; + } + + public long longValue() { + return value().longValue(); + } + + private static BigInteger newBigInt(int value) { + return BigInteger.valueOf(10).pow(value); + } + + public static String toFriendlyString(BigInteger value) { + if (value.compareTo(ETHER.value()) == 1 || value.compareTo(ETHER.value()) == 0) { + return Float.toString(value.divide(ETHER.value()).floatValue()) + " ETHER"; + } + else if(value.compareTo(FINNEY.value()) == 1 || value.compareTo(FINNEY.value()) == 0) { + return Float.toString(value.divide(FINNEY.value()).floatValue()) + " FINNEY"; + } + else if(value.compareTo(SZABO.value()) == 1 || value.compareTo(SZABO.value()) == 0) { + return Float.toString(value.divide(SZABO.value()).floatValue()) + " SZABO"; + } + else + return Float.toString(value.divide(WEI.value()).floatValue()) + " WEI"; + } +} diff --git a/core/src/main/java/org/platon/core/EventDispatchWorker.java b/core/src/main/java/org/platon/core/EventDispatchWorker.java new file mode 100644 index 0000000..90d3b9c --- /dev/null +++ b/core/src/main/java/org/platon/core/EventDispatchWorker.java @@ -0,0 +1,115 @@ +package org.platon.core; + +import org.platon.common.AppenderName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.concurrent.*; + +/** + * + * @author - Jungle + * @version 0.0.1 + * @date 2018/9/6 16:20 + */ +@Component +public class EventDispatchWorker { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + private static EventDispatchWorker instance; + + private static final int[] queueSizeWarnLevels = new int[]{0, 10_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 10_000_000}; + + // queue + private final BlockingQueue executorQueue = new LinkedBlockingQueue(); + + + private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, + TimeUnit.MILLISECONDS, executorQueue, r -> new Thread(r, "-EDW-") + ); + + private long taskStart; + private Runnable lastTask; + private int lastQueueSizeWarnLevel = 0; + private int counter; + + public static EventDispatchWorker getDefault() { + if (instance == null) { + instance = new EventDispatchWorker() { + @Override + public void invokeLater(Runnable r) { + r.run(); + } + }; + } + return instance; + } + + public void invokeLater(final Runnable task) { + + if (executor.isShutdown()) { + return; + } + if (counter++ % 1000 == 0) { + logStatus(); + } + executor.submit(() -> { + try { + lastTask = task; + taskStart = System.nanoTime(); + task.run(); + long t = (System.nanoTime() - taskStart) / 1_000_000; + taskStart = 0; + if (t > 1000) { + logger.warn("EDW task executed in more than 1 sec: " + t + "ms, " + + "Executor queue size: " + executorQueue.size()); + } + } catch (Exception e) { + logger.error("EDW task exception", e); + } + }); + } + + + private void logStatus() { + + int curLevel = getSizeWarnLevel(executorQueue.size()); + if (lastQueueSizeWarnLevel == curLevel) { + return; + } + + synchronized (this) { + if (curLevel > lastQueueSizeWarnLevel) { + long t = taskStart == 0 ? 0 : (System.nanoTime() - taskStart) / 1_000_000; + String msg = "EDW size grown up to " + executorQueue.size() + " (last task executing for " + t + " ms: " + lastTask; + if (curLevel < 3) { + logger.info(msg); + } else { + logger.warn(msg); + } + } else if (curLevel < lastQueueSizeWarnLevel) { + logger.info("EDW size shrunk down to " + executorQueue.size()); + } + lastQueueSizeWarnLevel = curLevel; + } + } + + private static int getSizeWarnLevel(int size) { + + int idx = Arrays.binarySearch(queueSizeWarnLevels, size); + return idx >= 0 ? idx : -(idx + 1) - 1; + } + + public void shutdown() { + executor.shutdownNow(); + try { + + executor.awaitTermination(10L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.warn("shutdown: executor interrupted: {}", e.getMessage()); + } + } +} diff --git a/core/src/main/java/org/platon/core/ExternalAccount.java b/core/src/main/java/org/platon/core/ExternalAccount.java new file mode 100644 index 0000000..457b240 --- /dev/null +++ b/core/src/main/java/org/platon/core/ExternalAccount.java @@ -0,0 +1,46 @@ +package org.platon.core; + +import java.math.BigInteger; + +/** + * ExternalAccount + * + * @author yanze + * @desc external account for platON + * @create 2018-07-26 17:26 + **/ +public class ExternalAccount extends Account{ + + /** + * nickName for im + */ + private byte[] nickName; + + /** + * contribution for consensus + */ + BigInteger contribution; + + public ExternalAccount(BigInteger balance, byte[] extraRoot, byte[] binHash, byte[] permissionRoot,byte[] nickName,BigInteger contribution) { + super(balance, extraRoot, binHash, permissionRoot); + this.nickName = nickName; + this.contribution = contribution; + } + + public ExternalAccount(byte[] protoBuf) { + super(protoBuf); + } + + @Override + public byte[] getEncoded() { + return super.getEncoded(); + } + + public byte[] getNickName() { + return nickName; + } + + public BigInteger getContribution() { + return contribution; + } +} diff --git a/core/src/main/java/org/platon/core/ImportResult.java b/core/src/main/java/org/platon/core/ImportResult.java new file mode 100644 index 0000000..7cb56ee --- /dev/null +++ b/core/src/main/java/org/platon/core/ImportResult.java @@ -0,0 +1,14 @@ +package org.platon.core; + +public enum ImportResult { + IMPORTED_BEST, + IMPORTED_NOT_BEST, + EXIST, + NO_PARENT, + INVALID_BLOCK, + CONSENSUS_BREAK; + + public boolean isSuccessful() { + return equals(IMPORTED_BEST) || equals(IMPORTED_NOT_BEST); + } +} diff --git a/core/src/main/java/org/platon/core/PendingStateIfc.java b/core/src/main/java/org/platon/core/PendingStateIfc.java new file mode 100644 index 0000000..bededa7 --- /dev/null +++ b/core/src/main/java/org/platon/core/PendingStateIfc.java @@ -0,0 +1,49 @@ +package org.platon.core; + +import org.platon.core.block.Block; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; + +import java.util.List; + +public interface PendingStateIfc { + + /** + * Adds transactions received from the net to the list of wire transactions
+ * Triggers an update of pending state + * + * @param transactions txs received from the net + * @return sublist of transactions with NEW_PENDING status + */ + List addPendingTransactions(List transactions); + + /** + * Adds transaction to the list of pending state txs
+ * For the moment this list is populated with txs sent by our peer only
+ * Triggers an update of pending state + */ + void addPendingTransaction(Transaction tx); + + /** + * It should be called on each block imported as BEST
+ * Does several things: + *

    + *
  • removes block's txs from pending state and wire lists
  • + *
  • removes outdated wire txs
  • + *
  • updates pending state
  • + *
+ * + * @param block block imported into blockchain as a BEST one + */ + void processBest(Block block, List receipts); + + /** + * @return pending state repository + */ + Repository getRepository(); + + /** + * @return list of pending transactions + */ + List getPendingTransactions(); +} diff --git a/core/src/main/java/org/platon/core/PendingStateImpl.java b/core/src/main/java/org/platon/core/PendingStateImpl.java new file mode 100644 index 0000000..006bca4 --- /dev/null +++ b/core/src/main/java/org/platon/core/PendingStateImpl.java @@ -0,0 +1,376 @@ +package org.platon.core; + +import org.apache.commons.collections4.map.LRUMap; +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.Numeric; +import org.platon.core.config.CommonConfig; +import org.platon.core.config.CoreConfig; +import org.platon.core.listener.PendingTransactionState; +import org.platon.core.listener.PlatonListener; +import org.platon.core.block.Block; +import org.platon.core.datasource.TransactionStore; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class PendingStateImpl implements PendingStateIfc { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PENDING); + + @Autowired + private CoreConfig config = CoreConfig.getInstance(); + + @Autowired + CommonConfig commonConfig; + + @Autowired + private PlatonListener listener; + + @Autowired + private BlockchainImpl blockchain; + + @Autowired + private BlockStoreIfc blockStore; + + @Autowired + private TransactionStore transactionStore; + + private final List pendingTransactions = new ArrayList<>(); + + + + private final Map receivedTxs = new LRUMap<>(100000); + private final Object dummyObject = new Object(); + + + private Repository pendingState; + + private Block best = null; + + @Autowired + public PendingStateImpl(final PlatonListener listener) { + this.listener = listener; + } + + public void init() { + this.pendingState = getOrigRepository().startTracking(); + } + + private Repository getOrigRepository() { + return blockchain.getRepositorySnapshot(); + } + + @Override + public synchronized Repository getRepository() { + if (pendingState == null) { + init(); + } + return pendingState; + } + + @Override + public synchronized List getPendingTransactions() { + List txs = pendingTransactions + .stream() + .map(PendingTransaction::getTransaction) + .collect(Collectors.toList()); + return txs; + } + + public Block getBestBlock() { + if (best == null) { + best = blockchain.getBestBlock(); + } + return best; + } + + private boolean addNewTxIfNotExist(Transaction tx) { + return receivedTxs.put(new ByteArrayWrapper(tx.getHash()), dummyObject) == null; + } + + @Override + public void addPendingTransaction(Transaction tx) { + addPendingTransactions(Collections.singletonList(tx)); + } + + @Override + public synchronized List addPendingTransactions(List transactions) { + + + int unknownTx = 0; + List newPending = new ArrayList<>(); + for (Transaction tx : transactions) { + + if (!addNewTxIfNotExist(tx)) { + continue; + } + if (!addPendingTransactionImpl(tx)) { + continue; + } + newPending.add(tx); + unknownTx++; + } + + if (logger.isDebugEnabled()) { + logger.debug("~> Pending transaction list added: total: {}, new: {}, valid (added to pending): {} (current #of known txs: {})", + transactions.size(), unknownTx, newPending, receivedTxs.size()); + } + if (!newPending.isEmpty()) { + listener.onPendingTransactionsReceived(newPending); + + listener.onPendingStateChanged(PendingStateImpl.this); + } + return newPending; + } + + private void fireTxUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("PendingTransactionUpdate: (Tot: %3s) %12s : %s %8s %s [%s]", + getPendingTransactions().size(), + state, Numeric.toHexString(txReceipt.getTransaction().getSender()).substring(0, 8), + txReceipt.getTransaction().getReferenceBlockNum(), + block.getShortDescr(), txReceipt.getError())); + } + listener.onPendingTransactionUpdate(txReceipt, state, block); + } + + + private boolean addPendingTransactionImpl(final Transaction tx) { + + + TransactionReceipt newReceipt = new TransactionReceipt(); + newReceipt.setTransaction(tx); + + String err = validate(tx); + + TransactionReceipt txReceipt; + if (err != null) { + txReceipt = createDroppedReceipt(tx, err); + fireTxUpdate(txReceipt, PendingTransactionState.DROPPED, getBestBlock()); + } else { + pendingTransactions.add(new PendingTransaction(tx, getBestBlock().getBlockHeader().getNumber())); + fireTxUpdate(newReceipt, PendingTransactionState.NEW_PENDING, getBestBlock()); + } + return err != null; + } + + private TransactionReceipt createDroppedReceipt(Transaction tx, String error) { + TransactionReceipt txReceipt = new TransactionReceipt(); + txReceipt.setTransaction(tx); + txReceipt.setError(error); + return txReceipt; + } + + + private String validate(Transaction tx) { + try { + tx.verify(); + } catch (Exception e) { + return String.format("Invalid transaction: %s", e.getMessage()); + } + if (config.getMineMinEnergonPrice().compareTo(tx.getEnergonPrice()) > 0) { + return "Too low gas price for transaction: " + tx.getEnergonPrice(); + } + return null; + } + + + private Block findCommonAncestor(Block b1, Block b2) { + while (!b1.isEqual(b2)) { + + if (b1.getBlockHeader().getNumber() >= b2.getBlockHeader().getNumber()) { + b1 = blockchain.getBlockByHash(b1.getBlockHeader().getParentHash()); + } + + if (b1.getBlockHeader().getNumber() < b2.getBlockHeader().getNumber()) { + b2 = blockchain.getBlockByHash(b2.getBlockHeader().getParentHash()); + } + if (b1 == null || b2 == null) { + throw new RuntimeException("Pending state can't find common ancestor: one of blocks has a gap"); + } + } + return b1; + } + + @Override + public synchronized void processBest(Block newBlock, List receipts) { + + + + if (getBestBlock() != null && !getBestBlock().isParentOf(newBlock)) { + + + + + + Block commonAncestor = findCommonAncestor(getBestBlock(), newBlock); + + if (logger.isDebugEnabled()) { + logger.debug("New best block from another fork: " + + newBlock.getShortDescr() + ", old best: " + getBestBlock().getShortDescr() + + ", ancestor: " + commonAncestor.getShortDescr()); + } + + Block rollback = getBestBlock(); + while (!rollback.isEqual(commonAncestor)) { + List blockTxs = new ArrayList<>(); + for (Transaction tx : rollback.getTransactions()) { + logger.trace("Returning transaction back to pending: " + tx); + blockTxs.add(new PendingTransaction(tx, commonAncestor.getBlockHeader().getNumber())); + } + + pendingTransactions.addAll(0, blockTxs); + + + rollback = blockchain.getBlockByHash(rollback.getBlockHeader().getParentHash()); + } + + + pendingState = getOrigRepository().getSnapshotTo(commonAncestor.getBlockHeader().getStateRoot()).startTracking(); + + + Block main = newBlock; + List mainFork = new ArrayList<>(); + while (!main.isEqual(commonAncestor)) { + mainFork.add(main); + main = blockchain.getBlockByHash(main.getBlockHeader().getParentHash()); + } + + + for (int i = mainFork.size() - 1; i >= 0; i--) { + processBestInternal(mainFork.get(i), null); + } + } else { + logger.debug("PendingStateImpl.processBest: " + newBlock.getShortDescr()); + processBestInternal(newBlock, receipts); + } + + best = newBlock; + + pendingState = getOrigRepository().startTracking(); + + listener.onPendingStateChanged(PendingStateImpl.this); + } + + private void processBestInternal(Block block, List receipts) { + + clearPending(block, receipts); + + clearOutdated(block.getBlockHeader().getNumber()); + } + + private void clearOutdated(final long blockNumber) { + + + + List outdated = new ArrayList<>(); + long txOutdatedThreshold = 128; + for (PendingTransaction tx : pendingTransactions) { + if (blockNumber - tx.getBlockNumber() > txOutdatedThreshold) { + outdated.add(tx); + + fireTxUpdate(createDroppedReceipt(tx.getTransaction(), + "Tx was not included into last " + txOutdatedThreshold + " blocks"), + PendingTransactionState.DROPPED, getBestBlock()); + } + } + + if (outdated.isEmpty()) { + return; + } + + if (logger.isDebugEnabled()) { + for (PendingTransaction tx : outdated) { + if (logger.isTraceEnabled()) { + logger.trace( + "Clear outdated pending transaction, block.number: [{}] hash: [{}]", + tx.getBlockNumber(), + Numeric.toHexString(tx.getHash()) + ); + } + } + } + pendingTransactions.removeAll(outdated); + } + + private void clearPending(Block block, List receipts) { + + for (int i = 0; i < block.getTransactions().size(); i++) { + Transaction tx = block.getTransactions().get(i); + PendingTransaction pend = new PendingTransaction(tx); + + if (pendingTransactions.remove(pend)) { + try { + if (logger.isTraceEnabled()) { + logger.trace("Clear pending transaction, hash: [{}]", Numeric.toHexString(tx.getHash())); + } + TransactionReceipt receipt; + if (receipts != null) { + receipt = receipts.get(i); + } else { + TransactionInfo info = getTransactionInfo(tx.getHash(), block.getBlockHeader().getHash()); + receipt = info.getReceipt(); + } + fireTxUpdate(receipt, PendingTransactionState.INCLUDED, block); + } catch (Exception e) { + logger.error("Exception creating onPendingTransactionUpdate (block: " + block.getShortDescr() + ", tx: " + i, e); + } + } + } + } + + private TransactionInfo getTransactionInfo(byte[] txHash, byte[] blockHash) { + TransactionInfo info = transactionStore.get(txHash, blockHash); + Transaction tx = blockchain.getBlockByHash(info.getBlockHash()).getTransactions().get(info.getIndex()); + info.getReceipt().setTransaction(tx); + return info; + } + + + private Block createFakePendingBlock() { + // creating fake lightweight calculated block with no hashes calculations + Block block = new Block( + best.getBlockHeader().getTimestamp() + 1, // timestamp + new byte[32], // author + new byte[32], // coinbase + best.getBlockHeader().getHash(), // parentHash + new byte[32], // bloomLog + best.getBlockHeader().getNumber() + 1, // number + BigInteger.ZERO, // energonUsed + BigInteger.valueOf(Long.MAX_VALUE), // energonCeiling + BigInteger.ZERO, // difficulty + new byte[0] // extraData + ); + block.getBlockHeader().setRoots( + new byte[32], // stateRoot + new byte[32], // permissionRoot + new byte[32], // dposRoot + new byte[32], // transactionRoot + new byte[32], // transferRoot + new byte[32], // votingRoot + new byte[32] // receiptRoot + ); + return block; + } + + @Autowired + public void setBlockchain(BlockchainImpl blockchain) { + this.blockchain = blockchain; + this.blockStore = blockchain.getBlockStore(); + //this.programInvokeFactory = blockchain.getProgramInvokeFactory(); + this.transactionStore = blockchain.getTransactionStore(); + } +} diff --git a/core/src/main/java/org/platon/core/PendingTransaction.java b/core/src/main/java/org/platon/core/PendingTransaction.java new file mode 100644 index 0000000..18ebce6 --- /dev/null +++ b/core/src/main/java/org/platon/core/PendingTransaction.java @@ -0,0 +1,95 @@ +package org.platon.core; + +import org.platon.common.utils.ByteUtil; +import org.platon.core.transaction.Transaction; + +import java.math.BigInteger; +import java.util.Arrays; + +public class PendingTransaction { + + private Transaction transaction; + + private long blockNumber; + + public PendingTransaction(byte[] bytes) { + parse(bytes); + } + + public PendingTransaction(Transaction transaction) { + this(transaction, 0); + } + + public PendingTransaction(Transaction transaction, long blockNumber) { + this.transaction = transaction; + this.blockNumber = blockNumber; + } + + public Transaction getTransaction() { + return transaction; + } + + public long getBlockNumber() { + return blockNumber; + } + + public byte[] getSender() { + return transaction.getSender(); + } + + public byte[] getHash() { + return transaction.getHash(); + } + + public byte[] getBytes() { + byte[] numberBytes = BigInteger.valueOf(blockNumber).toByteArray(); + byte[] txBytes = transaction.getEncoded(); + byte[] bytes = new byte[1 + numberBytes.length + txBytes.length]; + + bytes[0] = (byte) numberBytes.length; + System.arraycopy(numberBytes, 0, bytes, 1, numberBytes.length); + + System.arraycopy(txBytes, 0, bytes, 1 + numberBytes.length, txBytes.length); + + return bytes; + } + + private void parse(byte[] bytes) { + byte[] numberBytes = new byte[bytes[0]]; + byte[] txBytes = new byte[bytes.length - 1 - numberBytes.length]; + + System.arraycopy(bytes, 1, numberBytes, 0, numberBytes.length); + + System.arraycopy(bytes, 1 + numberBytes.length, txBytes, 0, txBytes.length); + + this.blockNumber = new BigInteger(numberBytes).longValue(); + this.transaction = new Transaction(txBytes); + } + + @Override + public String toString() { + return "PendingTransaction [" + + " transaction=" + transaction + + ", blockNumber=" + blockNumber + + ']'; + } + + /** + * Two pending transaction are equal if equal their sender + nonce + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PendingTransaction)) return false; + + PendingTransaction that = (PendingTransaction) o; + + return Arrays.equals(getSender(), that.getSender()) && + (transaction.getReferenceBlockNum() == that.getTransaction().getReferenceBlockNum()); + } + + @Override + public int hashCode() { + return ByteUtil.byteArrayToInt(getSender()) + ((int)transaction.getReferenceBlockNum()); + } +} diff --git a/core/src/main/java/org/platon/core/Permission.java b/core/src/main/java/org/platon/core/Permission.java new file mode 100644 index 0000000..7863d98 --- /dev/null +++ b/core/src/main/java/org/platon/core/Permission.java @@ -0,0 +1,80 @@ +package org.platon.core; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.bouncycastle.util.encoders.Hex; +import org.platon.core.proto.PermissionAddrProto; +import org.platon.storage.trie.SecureTrie; +import org.platon.storage.datasource.CachedSource; +import org.platon.storage.datasource.Source; +import org.platon.storage.datasource.WriteCache; +import org.platon.storage.trie.Trie; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; + +/** + * Permission + * + * @author yanze + * @desc Account's permission + * @create 2018-07-27 15:55 + **/ +public class Permission { + + private static final Logger logger = LoggerFactory.getLogger(Permission.class); + + private byte[] permissionRoot; + + private boolean dirty; + + private CachedSource.BytesKey trieCache; + private Trie permissionTrie; + + public Permission(Source stateDS, byte[] permissionRoot){ + + trieCache = new WriteCache.BytesKey<>(stateDS, WriteCache.CacheType.COUNTING); + permissionTrie = new SecureTrie(trieCache, permissionRoot); + + this.permissionRoot = permissionRoot; + this.dirty = false; + } + + /** + * auth have this permission + * @param address auth address + * @param url permission url + * @return -true:auth access -false:auth fail + */ + public boolean auth(byte[] address,byte[] url){ + return auth(address,url,true); + } + + private boolean auth(byte[] address,byte[] url,boolean isCycle){ + try { + PermissionAddrProto permissionAddrProto = PermissionAddrProto.parseFrom(permissionTrie.get(url)); + List addressList = permissionAddrProto.getAddressList().getEleList(); + List urlList = permissionAddrProto.getUrlList().getEleList(); + //check address is contain + for(ByteString addressBStr : addressList){ + if(Arrays.equals(address,addressBStr.toByteArray())){ + return true; + } + } + if(isCycle){ + //check url is contain + for(ByteString urlBStr : urlList){ + //isCycle is false for prevent infinite loops + auth(address,urlBStr.toByteArray(),false); + } + } + return false; + } catch (InvalidProtocolBufferException e) { + logger.error("auth error,address:"+Hex.toHexString(address),e); + return false; + } + } + +} diff --git a/core/src/main/java/org/platon/core/Repository.java b/core/src/main/java/org/platon/core/Repository.java new file mode 100644 index 0000000..9c3f7ba --- /dev/null +++ b/core/src/main/java/org/platon/core/Repository.java @@ -0,0 +1,108 @@ +package org.platon.core; + +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.wrapper.DataWord; +import org.platon.core.block.Block; +import org.platon.core.db.ContractDetails; + +import javax.annotation.Nullable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author - Jungle + * @date 2018/9/3 14:29 + * @version 0.0.1 + */ +public interface Repository { + + boolean isContract(byte[] addr); + + Account createAccount(byte[] addr); + + BigInteger addBalance(byte[] addr, BigInteger value); + + BigInteger subBalance(byte[] addr, BigInteger value); + + BigInteger getBalance(byte[] addr); + + boolean isExist(byte[] addr); + + Account getAccount(byte[] addr); + + void delete(byte[] addr); + + ContractDetails getContractDetails(byte[] addr); + + boolean hasContractDetails(byte[] addr); + + void saveCode(byte[] addr, byte[] code); + + byte[] getCode(byte[] addr); + + byte[] getCodeHash(byte[] addr); + + void addStorageRow(byte[] addr, DataWord key, DataWord value); + + DataWord getStorageValue(byte[] addr, DataWord key); + + Set getAccountsKeys(); + + void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash); + + Repository startTracking(); + + void flush(); + + void flushNoReconnect(); + + void commit(); + + void rollback(); + + void syncToRoot(byte[] root); + + boolean isClosed(); + + void close(); + + void reset(); + + void updateBatch(HashMap accountStates, + HashMap contractDetailes); + + byte[] getRoot(); + + void loadAccount(byte[] addr, HashMap cacheAccounts, + HashMap cacheDetails); + + Repository getSnapshotTo(byte[] root); + + /** + * Retrieve storage size for a given account + * + * @param addr of the account + * @return storage entries count + */ + int getStorageSize(byte[] addr); + + /** + * Retrieve all storage keys for a given account + * + * @param addr of the account + * @return set of storage keys or empty set if account with specified address not exists + */ + Set getStorageKeys(byte[] addr); + + /** + * Retrieve storage entries from an account for given keys + * + * @param addr of the account + * @param keys + * @return storage entries for specified keys, or full storage if keys parameter is null + */ + Map getStorage(byte[] addr, @Nullable Collection keys); +} diff --git a/core/src/main/java/org/platon/core/TransactionExecutionSummary.java b/core/src/main/java/org/platon/core/TransactionExecutionSummary.java new file mode 100644 index 0000000..24ffee4 --- /dev/null +++ b/core/src/main/java/org/platon/core/TransactionExecutionSummary.java @@ -0,0 +1,5 @@ +package org.platon.core; + +public class TransactionExecutionSummary { + +} diff --git a/core/src/main/java/org/platon/core/TransactionInfo.java b/core/src/main/java/org/platon/core/TransactionInfo.java new file mode 100644 index 0000000..9686554 --- /dev/null +++ b/core/src/main/java/org/platon/core/TransactionInfo.java @@ -0,0 +1,46 @@ +package org.platon.core; + +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; + +public class TransactionInfo { + + TransactionReceipt receipt; + byte[] blockHash; + byte[] parentBlockHash; + int index; + + public TransactionInfo(TransactionReceipt receipt, byte[] blockHash, int index) { + this.receipt = receipt; + this.blockHash = blockHash; + this.index = index; + } + + public TransactionInfo(TransactionReceipt receipt) { + this.receipt = receipt; + } + + public void setTransaction(Transaction tx){ + this.receipt.setTransaction(tx); + } + + public TransactionReceipt getReceipt(){ + return receipt; + } + + public byte[] getBlockHash() { return blockHash; } + + public byte[] getParentBlockHash() { + return parentBlockHash; + } + + public void setParentBlockHash(byte[] parentBlockHash) { + this.parentBlockHash = parentBlockHash; + } + + public int getIndex() { return index; } + + public boolean isPending() { + return blockHash == null; + } +} diff --git a/core/src/main/java/org/platon/core/TrieHash.java b/core/src/main/java/org/platon/core/TrieHash.java new file mode 100644 index 0000000..3a2ac9c --- /dev/null +++ b/core/src/main/java/org/platon/core/TrieHash.java @@ -0,0 +1,31 @@ +package org.platon.core; + +import org.platon.crypto.HashUtil; +import org.platon.storage.trie.Trie; +import org.platon.storage.trie.TrieImpl; + +import java.util.List; + +/** + * Created by alliswell on 2018/8/6. + */ +public class TrieHash { + + /** + * Calculate the rootHash of a list of bytes + * note: this function does NOT save the k-v into DB, just calculate the hash + */ + public static byte[] getTrieRoot(List values) { + Trie trie = new TrieImpl(); + + if (values == null || values.isEmpty()) + return HashUtil.EMPTY_HASH; + + for (int i = 0; i < values.size(); i++) { + trie.put(new byte[] {(byte)((i >> 24) & 0xFF), (byte)((i >> 16) & 0xFF), (byte)((i >> 8) & 0xFF),(byte)(i & 0xFF)}, + values.get(i)); + } + return trie.getRootHash(); + } + +} diff --git a/core/src/main/java/org/platon/core/Worker.java b/core/src/main/java/org/platon/core/Worker.java new file mode 100644 index 0000000..481a73e --- /dev/null +++ b/core/src/main/java/org/platon/core/Worker.java @@ -0,0 +1,147 @@ +package org.platon.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.Thread.sleep; + +/** + * worker thread common + * + * @author alliswell + * @since 2018/08/22 + */ +public abstract class Worker implements Runnable { + + /** + * logger handle + */ + private static final Logger logger = LoggerFactory.getLogger(Worker.class); + + /** + * the status of the thread + * 0: Stopped + * 1: Starting + * 2: Started + * 3: Stopping + * 4: Killing + */ + private AtomicInteger status = new AtomicInteger(0); + private enum WorkerState { + Stopped((int)0), + Starting((int)1), + Started((int)2), + Stopping((int)3), + Killing((int)4); + + private int value; + WorkerState(int value) { + this.value = value; + } + public int getValue() { + return value; + } + } + /** + * idle time in milliseconds + */ + private int idleInMills; + + protected void setIdleInMills(int idleInMills) { + this.idleInMills = idleInMills; + } + + /* + * @Author alliswell + * @Description do the business work accordingly + * @Date 9:46 2018/8/23 + **/ + protected abstract void doWork(); + + /* + * @Author alliswell + * @Description keep the thread run when status is Started + * @Date 18:37 2018/8/22 + **/ + protected void keepWorking() { + + while (WorkerState.Started.getValue() == status.get()) { + doWork(); + + try { + sleep(idleInMills); + } catch (InterruptedException e) { + logger.error("worker was interrupted!"); + e.printStackTrace(); + return; + } + } + } + + @Override + public void run() { + + status.set(WorkerState.Starting.getValue()); + while (WorkerState.Killing.getValue() != status.get()) { + try { + status.compareAndSet(WorkerState.Starting.getValue(), WorkerState.Started.getValue()); + + beforeWorking(); + keepWorking(); + doneWorking(); + + status.updateAndGet(x -> x = (x==WorkerState.Started.getValue() || x==WorkerState.Killing.getValue() )? x: WorkerState.Starting.getValue()); + if(status.get() == WorkerState.Stopped.getValue()) { + sleep(20); + } + + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + logger.error(e.getMessage()); + } + } + + stopWorking(); + } + + /** + * ready to start work + */ + protected void beforeWorking() { + + } + + /** + * run after keepWorking() + */ + protected void doneWorking() { + status.set(WorkerState.Stopping.getValue()); + } + + /** + * stop worker thread + */ + protected void stopWorking() { + if(!status.compareAndSet(WorkerState.Stopping.getValue(), WorkerState.Stopped.getValue())) { + try { + sleep(20); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + } + + /** + * if current thread is working + */ + public boolean isWorking() { + return status.get() == WorkerState.Started.getValue(); + } + + public void terminate() { + status.set(WorkerState.Killing.getValue()); + } +} diff --git a/core/src/main/java/org/platon/core/block/Block.java b/core/src/main/java/org/platon/core/block/Block.java new file mode 100644 index 0000000..77f75cc --- /dev/null +++ b/core/src/main/java/org/platon/core/block/Block.java @@ -0,0 +1,129 @@ +package org.platon.core.block; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.utils.Numeric; +import org.platon.core.block.proto.BlockProto; +import org.platon.core.transaction.Transaction; + +import java.math.BigInteger; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Created by alliswell on 2018/7/25. + */ +public class Block { + + private BlockHeader header; + private byte[] protoData; + private List transactions = new CopyOnWriteArrayList<>(); + + private boolean parsed = false; + + public Block(long timestamp, byte[] author, byte[] coinbase, byte[] parentHash, byte[] bloomLog, long number, + BigInteger energonUsed, BigInteger energonCeiling, BigInteger difficulty, byte[] extraData) { + this.header = new BlockHeader(timestamp, author,coinbase, parentHash, bloomLog, + number, energonUsed, energonCeiling, difficulty, extraData); + + } + + private Block() { + } + + public Block(byte[] protoBytes) { + if (!parsed) { + parse(protoBytes); + } + } + + public BigInteger getTotalDifficulty() { + return this.header.getDifficulty(); + } + + public void setTotalDifficulty(BigInteger totalDifficulty) { + this.header.setDifficulty(totalDifficulty); + } + + public BlockHeader getBlockHeader() { + return header; + } + + public List getTransactions() { + return transactions; + } + + public void setTransactions(List transactions) { + this.transactions = transactions; + } + + public void setAuthor(byte[] author) { + header.setAuthor(author); + } + + public int getBlockSize() { + if (null == protoData) { + return 0; + } + return protoData.length; + } + + public byte[] encode() { + if (parsed || null != protoData) { + return protoData; + } + BlockProto.Block.Builder bodyBuilder = BlockProto.Block.newBuilder(); + bodyBuilder.setHeaderbytes(ByteString.copyFrom(header.encode())); + for (int i = 0; i < this.transactions.size(); ++i) { + bodyBuilder.addTransactionbytes(ByteString.copyFrom(transactions.get(i).getEncoded())); + } + protoData = bodyBuilder.build().toByteArray(); + return protoData; + } + + private void parse(byte[] bytes) { + if (parsed) + return; + try { + BlockProto.Block block = BlockProto.Block.parseFrom(bytes); + this.header = new BlockHeader(block.getHeaderbytes().toByteArray()); + for (int i = 0; i < block.getTransactionbytesCount(); ++i) { + this.transactions.add(new Transaction(block.getTransactionbytes(i).toByteArray())); + } + for (int i = 0; i < block.getUnclesbytesCount(); ++i) { + //TODO: write uncles + } + for (int i = 0; i < block.getSignaturesCount(); ++i) { + //TODO: write signatures + } + this.parsed = true; + protoData = bytes; + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + public boolean isEqual(Block block) { + return Arrays.areEqual(this.getBlockHeader().getHash(), block.getBlockHeader().getHash()); + } + + public boolean isGenesis() { + return this.header.getNumber() == 0; + } + + public boolean isParentOf(Block block) { + return Arrays.areEqual(this.header.getHash(), block.header.getParentHash()); + } + + public String getShortHash() { + String hexHash = Numeric.toHexString(this.header.getHash()); + return hexHash.substring(0, 6); + } + + public String getShortDescr() { + return "#" + this.header.getNumber() + " (" + Hex.toHexString(this.header.getHash()).substring(0, 6) + " <~ " + + Hex.toHexString(this.header.getParentHash()).substring(0, 6) + ") Txs:" + getTransactions().size() + "."; + } +} diff --git a/core/src/main/java/org/platon/core/block/BlockHeader.java b/core/src/main/java/org/platon/core/block/BlockHeader.java new file mode 100644 index 0000000..1a5111f --- /dev/null +++ b/core/src/main/java/org/platon/core/block/BlockHeader.java @@ -0,0 +1,433 @@ +package org.platon.core.block; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.core.block.proto.BlockHeaderProto; +import org.platon.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +/** + * the block's head + * Created by alliswell on 2018/7/26. + */ +public class BlockHeader { + + private static final Logger logger = LoggerFactory.getLogger(BlockHeader.class); + + /** + * the block product time in millionseconds + **/ + private long timestamp; + + /** + * address of the author + **/ + private byte[] author; + + /** + * father block's hash + **/ + private byte[] parentHash; + + /** + * root of the state MPT + **/ + private byte[] stateRoot; + + /** + * root of the permission MPT + **/ + private byte[] permissionRoot; + + /** + * root of the dpos list MPT + **/ + private byte[] dposRoot; + + /** + * root of the transactions MPT + **/ + private byte[] transactionRoot; + + /** + * root of the transfer MPT + **/ + private byte[] transferRoot; + + /** + * root of the voting MPT + **/ + private byte[] votingRoot; + + /** + * root of the receipt MPT + **/ + private byte[] receiptRoot; + + /** + * log bloom + **/ + private byte[] bloomLog; + + private long number; + + private BigInteger energonUsed; + + private BigInteger energonCeiling; + + private BigInteger difficulty; + + private byte[] extraData; + + private byte[] coinbase; + + /** + * sha3 of the whole Block + **/ + private byte[] hash; + + public BlockHeader() { + } + + public BlockHeader(long timestamp, byte[] author, byte[] coinbase, byte[] parentHash, byte[] bloomLog, long number, BigInteger energonUsed, BigInteger energonCeiling, BigInteger difficulty, byte[] extraData) { + this.timestamp = timestamp; + this.author = author; + this.coinbase = coinbase; + this.parentHash = parentHash; + this.bloomLog = bloomLog; + this.number = number; + this.energonUsed = energonUsed; + this.energonCeiling = energonCeiling; + this.difficulty = difficulty; + this.extraData = extraData; + } + + public BlockHeader(byte[] bytes) { + parse(bytes); + } + + public static byte[] headerHashFromBlock(byte[] _block) { + return new byte[0]; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public byte[] getAuthor() { + return author; + } + + public void setAuthor(byte[] author) { + + this.author = author; + } + + public byte[] getParentHash() { + if (null == parentHash || 0 == parentHash.length) { + return HashUtil.EMPTY_HASH; + } + return parentHash; + } + + public void setParentHash(byte[] parentHash) { + this.parentHash = parentHash; + } + + public byte[] getStateRoot() { + return stateRoot; + } + + public void setStateRoot(byte[] stateRoot) { + this.stateRoot = stateRoot; + } + + public void setPermissionRoot(byte[] permissionRoot) { + this.permissionRoot = permissionRoot; + } + + public void setDposRoot(byte[] dposRoot) { + this.dposRoot = dposRoot; + } + + public void setTransactionRoot(byte[] transactionRoot) { + this.transactionRoot = transactionRoot; + } + + public void setTransferRoot(byte[] transferRoot) { + this.transferRoot = transferRoot; + } + + public void setVotingRoot(byte[] votingRoot) { + this.votingRoot = votingRoot; + } + + public void setReceiptRoot(byte[] receiptRoot) { + this.receiptRoot = receiptRoot; + } + + public byte[] getPermissionRoot() { + return permissionRoot; + } + + public byte[] getDposRoot() { + return dposRoot; + } + + public byte[] getTransactionRoot() { + return transactionRoot; + } + + public byte[] getTransferRoot() { + return transferRoot; + } + + public byte[] getVotingRoot() { + return votingRoot; + } + + public byte[] getReceiptRoot() { + return receiptRoot; + } + + public byte[] getBloomLog() { + return bloomLog; + } + + public void setBloomLog(byte[] bloomLog) { + this.bloomLog = bloomLog; + } + + public BigInteger getEnergonUsed() { + return energonUsed; + } + + public void setEnergonUsed(BigInteger energonUsed) { + this.energonUsed = energonUsed; + } + + public BigInteger getEnergonCeiling() { + return energonCeiling; + } + + public void setEnergonCeiling(BigInteger energonCeiling) { + this.energonCeiling = energonCeiling; + } + + public BigInteger getDifficulty() { + return difficulty; + } + + public void setDifficulty(BigInteger difficulty) { + this.difficulty = difficulty; + } + + @Override + public int hashCode() { + if (null == hash || 0 == hash.length) { + hash = getHash(); + } + return Objects.hash(hash); + } + + public byte[] getHash() { + if (null != hash) { + return hash; + } + hash = HashUtil.sha3(encode()); + return hash; + } + + public byte[] getExtraData() { + return extraData; + } + + public void setExtraData(byte[] extraData) { + this.extraData = extraData; + } + + public void verify(BlockHeader parent, byte[] block) { + + } + + public void setRoots(byte[] stateRoot, byte[] permissionRoot, + byte[] dposRoot, byte[] transactionRoot, byte[] transferRoot, + byte[] votingRoot, byte[] receiptRoot) { + if (null == stateRoot || 0 == stateRoot.length) { + logger.error("state root is invalid!"); + } + if (null == permissionRoot || 0 == permissionRoot.length) { + logger.error("permission root is invalid!"); + } + if (null == dposRoot || 0 == dposRoot.length) { + logger.error("dpos root is invalid!"); + } + if (null == transactionRoot || 0 == transactionRoot.length) { + logger.error("transaction root is invalid!"); + } + if (null == transferRoot || 0 == transferRoot.length) { + logger.error("transfer root is invalid!"); + } + if (null == votingRoot || 0 == votingRoot.length) { + logger.error("voting root is invalid!"); + } + if (null == receiptRoot || 0 == receiptRoot.length) { + logger.error("receipt root is invalid!"); + } + this.stateRoot = stateRoot; + this.permissionRoot = permissionRoot; + this.dposRoot = dposRoot; + this.transactionRoot = transactionRoot; + this.transferRoot = transferRoot; + this.votingRoot = votingRoot; + this.receiptRoot = receiptRoot; + } + + public void propagatedBy(BlockHeader parent) { + stateRoot = parent.getStateRoot(); + permissionRoot = parent.getPermissionRoot(); + dposRoot = parent.getDposRoot(); + transactionRoot = parent.getTransactionRoot(); + transferRoot = parent.getTransferRoot(); + votingRoot = parent.getVotingRoot(); + receiptRoot = parent.getReceiptRoot(); + + number = parent.getNumber() + 1; + parentHash = parent.getHash(); + energonCeiling = parent.energonCeiling; + difficulty = new BigInteger("1"); + energonUsed = new BigInteger("0"); + timestamp = System.currentTimeMillis(); + } + + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + + public byte[] getCoinbase() { + return coinbase; + } + + public void setCoinbase(byte[] coinbase) { + this.coinbase = coinbase; + } + + public byte[] encode() { + BlockHeaderProto.BlockHeader.Builder headerBuilder = BlockHeaderProto.BlockHeader.newBuilder(); + + headerBuilder.setTimestamp(this.timestamp); + headerBuilder.setNumber(this.number); + if (null == this.parentHash || + null == this.stateRoot || + null == this.transactionRoot || + null == this.receiptRoot) { + return null; + } + headerBuilder.setParentHash(ByteString.copyFrom(this.parentHash)); + headerBuilder.setStateRoot(ByteString.copyFrom(this.stateRoot)); + headerBuilder.setTransactionRoot(ByteString.copyFrom(this.transactionRoot)); + headerBuilder.setReceiptRoot(ByteString.copyFrom(this.receiptRoot)); + if (null != this.permissionRoot) { + headerBuilder.setPermissionRoot(ByteString.copyFrom(this.permissionRoot)); + } + if (null != this.dposRoot) { + headerBuilder.setDposRoot(ByteString.copyFrom(this.dposRoot)); + } + if (null != this.transferRoot) { + headerBuilder.setTransferRoot(ByteString.copyFrom(this.transferRoot)); + } + if (null != this.votingRoot) { + headerBuilder.setVotingRoot(ByteString.copyFrom(this.votingRoot)); + } + if (null != this.bloomLog) { + headerBuilder.setBloomLog(ByteString.copyFrom(this.bloomLog)); + } + if (null != this.energonUsed) { + headerBuilder.setEnergonUsed(ByteString.copyFrom(this.energonUsed.toByteArray())); + } + if (null != this.energonCeiling) { + headerBuilder.setEnergonCeiling(ByteString.copyFrom(this.energonCeiling.toByteArray())); + } + if (null != this.difficulty) { + headerBuilder.setDifficulty(ByteString.copyFrom(this.difficulty.toByteArray())); + } + if (null != this.extraData) { + headerBuilder.setExtraData(ByteString.copyFrom(this.extraData)); + } + if (null != this.author) { + headerBuilder.setAuthor(ByteString.copyFrom(this.author)); + } + if (null != this.coinbase) { + headerBuilder.setCoinbase(ByteString.copyFrom(this.coinbase)); + } + + return headerBuilder.build().toByteArray(); + } + + private void parse(byte[] bytes) { + try { + BlockHeaderProto.BlockHeader blockHeader = BlockHeaderProto.BlockHeader.parseFrom(bytes); + + this.timestamp = blockHeader.getTimestamp(); + this.number = blockHeader.getNumber(); + this.parentHash = blockHeader.getParentHash().toByteArray(); + this.stateRoot = blockHeader.getStateRoot().toByteArray(); + this.permissionRoot = blockHeader.getPermissionRoot().toByteArray(); + this.dposRoot = blockHeader.getDposRoot().toByteArray(); + this.transactionRoot = blockHeader.getTransactionRoot().toByteArray(); + this.transferRoot = blockHeader.getTransferRoot().toByteArray(); + this.votingRoot = blockHeader.getVotingRoot().toByteArray(); + this.receiptRoot = blockHeader.getReceiptRoot().toByteArray(); + this.bloomLog = blockHeader.getBloomLog().toByteArray(); + this.energonUsed = new BigInteger(blockHeader.getEnergonUsed().toByteArray()); + this.energonCeiling = new BigInteger(blockHeader.getEnergonCeiling().toByteArray()); + this.difficulty = new BigInteger(blockHeader.getDifficulty().toByteArray()); + this.extraData = blockHeader.getExtraData().toByteArray(); + this.author = blockHeader.getAuthor().toByteArray(); + this.coinbase = blockHeader.getCoinbase().toByteArray(); + + this.hash = HashUtil.sha3(bytes); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof BlockHeader)) return false; + BlockHeader bh = (BlockHeader) other; + + return this.timestamp == bh.getTimestamp() && + Arrays.equals(this.author, bh.getAuthor()) && + Arrays.equals(this.coinbase, bh.getCoinbase()) && + Arrays.equals(this.parentHash, bh.getParentHash()) && + Arrays.equals(this.stateRoot, bh.getStateRoot()) && + Arrays.equals(this.permissionRoot, bh.getPermissionRoot()) && + Arrays.equals(this.dposRoot, bh.getDposRoot()) && + Arrays.equals(this.transactionRoot, bh.getTransactionRoot()) && + Arrays.equals(this.transferRoot, bh.getTransferRoot()) && + Arrays.equals(this.votingRoot, bh.getVotingRoot()) && + Arrays.equals(this.receiptRoot, bh.getReceiptRoot()) && + Arrays.equals(this.bloomLog, bh.getBloomLog()) && + Arrays.equals(this.extraData, bh.getExtraData()) && + Arrays.equals(this.hash, bh.getHash()) && + this.number == bh.getNumber() && + this.energonUsed.equals(bh.getEnergonUsed()) && + this.energonCeiling.equals(bh.getEnergonCeiling()) && + this.difficulty.equals(bh.getDifficulty()); + } + +} diff --git a/core/src/main/java/org/platon/core/block/BlockInfo.java b/core/src/main/java/org/platon/core/block/BlockInfo.java new file mode 100644 index 0000000..cfbed77 --- /dev/null +++ b/core/src/main/java/org/platon/core/block/BlockInfo.java @@ -0,0 +1,154 @@ +package org.platon.core.block; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.core.block.proto.BlockProto; + +import java.math.BigInteger; +import java.util.ArrayList; + +/** + * block detail info + * Created by alliswell on 2018/8/3. + */ + +public class BlockInfo { + /** + * block height + **/ + private long number; + + /** + * only inuse when consensus is POW + **/ + private BigInteger totalDifficulty; + + /** + * parent hash + **/ + private byte[] parentHash; + + /** + * children block in bytes + **/ + private ArrayList children; + + private ArrayList bloomLogs; + + private ArrayList txReceipts; + + private boolean parsed = false; + + public BlockInfo(byte[] bytes) { + init(); + if (!parsed) { + parse(bytes); + } + } + + public BlockInfo() { + init(); + } + + private void init() { + this.children = new ArrayList<>(); + this.bloomLogs = new ArrayList<>(); + this.txReceipts = new ArrayList<>(); + this.totalDifficulty = BigInteger.ZERO; + } + + public boolean isNull() { + return number == -1; + } + + public void setNumber(long number) { + this.number = number; + } + + public void setTotalDifficulty(BigInteger totalDifficulty) { + this.totalDifficulty = totalDifficulty; + } + + public void setParentHash(byte[] parentHash) { + this.parentHash = parentHash; + } + + public void addChild(byte[] child) { + this.children.add(child); + } + + public ArrayList getBloomLogs() { + return bloomLogs; + } + + public void addBloomLogs(byte[] bloomlog) { + this.bloomLogs.add(bloomlog); + + } + + public long getNumber() { + return number; + } + + public BigInteger getTotalDifficulty() { + return totalDifficulty; + } + + public byte[] getParentHash() { + return parentHash; + } + + public ArrayList getChildren() { + return children; + } + + public byte[] encode() { + BlockProto.BlockInfo.Builder infoBuilder = BlockProto.BlockInfo.newBuilder(); + + infoBuilder.setNumber(this.number); + if (null != this.parentHash) { + infoBuilder.setParentHash(ByteString.copyFrom(this.parentHash)); + } + for (int i = 0; i < children.size(); ++i) { + infoBuilder.addChildren(ByteString.copyFrom(children.get(i))); + } + for (int i = 0; i < bloomLogs.size(); ++i) { + infoBuilder.addBloomlog(ByteString.copyFrom(bloomLogs.get(i))); + } +// for(int i=0; i 0) { + this.totalDifficulty = new BigInteger(blockInfo.getTotalDifficulty().toByteArray()); + } + + if (blockInfo.getParentHash() != null) { + this.parentHash = blockInfo.getParentHash().toByteArray(); + } + for (int i = 0; i < blockInfo.getChildrenCount(); ++i) { + this.children.add(blockInfo.getChildren(i).toByteArray()); + } + for (int i = 0; i < blockInfo.getBloomlogCount(); ++i) { + this.bloomLogs.add(blockInfo.getBloomlog(i).toByteArray()); + } +// for(int i=0; i rawBlocks; + + /** + * blocks have been checked and confirmed + */ + private final ArrayBlockingQueue validBlocks; + + /** + * blocks have been checked but not confirmed + */ + private final ArrayBlockingQueue forkBlocks; + + /** + * map of hash->signature count + */ + private final ConcurrentHashMap> signCount; + + public BlockPool(int capacity) { + // init queue + rawBlocks = new ArrayBlockingQueue<>(capacity); + validBlocks = new ArrayBlockingQueue<>(capacity); + forkBlocks = new ArrayBlockingQueue<>(capacity); + signCount = new ConcurrentHashMap<>(capacity); + } + + public void injectRawBlock(Block block) { + try(AutoLock lock = new AutoLock(rawLock)){ + if (rawBlocks.contains(block)) { + logger.error("the block had already in pool! hash:" + Numeric.toHexString(block.getBlockHeader().getHash())); + return; + } + rawBlocks.offer(block); + } + } + + public Block pollRawBlock() { + try(AutoLock l = new AutoLock(rawLock)){ + return rawBlocks.poll(); + } + } + + public void injectValidBlock(Block block) { + try(AutoLock l = new AutoLock(validLock)){ + if (validBlocks.contains(block)) { + validBlocks.remove(block); + } + validBlocks.offer(block); + } + } + + public void injectForkBlock(Block block) { + try(AutoLock l = new AutoLock(forkLock)){ + if (forkBlocks.contains(block)) { + forkBlocks.remove(block); + } + forkBlocks.offer(block); + } + } + + public Block pollValidBlock() { + try(AutoLock l = new AutoLock(validLock)){ + return validBlocks.poll(); + } + } + + public Block pollForkBlock() { + try(AutoLock l = new AutoLock(forkLock)){ + Block b = forkBlocks.poll(); + return b; + } + } + + public void addSignatures(byte[] hash, byte[] sig) { + Set sigsSet = new HashSet<>(); + try(AutoLock l = new AutoLock(signLock)){ + if (signCount.containsKey(hash)) { + sigsSet = signCount.get(hash); + } + sigsSet.add(sig); + signCount.put(new ByteArrayWrapper(hash), sigsSet); + } + } + + public boolean hasRawBlocks() { + return !rawBlocks.isEmpty(); + } + + public boolean hasValidBlocks() { + return !validBlocks.isEmpty(); + } + + public int getSignaturesCount(byte[] hash) { + try(AutoLock l = new AutoLock(signLock)){ + Set signatures = signCount.get(hash); + return signatures.size(); + } + } + + public synchronized void clear() { + try(AutoLock l = new AutoLock(rawLock)) { + rawBlocks.clear(); + } + try(AutoLock l = new AutoLock(forkLock)) { + forkBlocks.clear(); + } + try(AutoLock l = new AutoLock(validLock)) { + validBlocks.clear(); + } + try (AutoLock l = new AutoLock(signLock)){ + signCount.clear(); + } + } +} diff --git a/core/src/main/java/org/platon/core/block/GenesisBlock.java b/core/src/main/java/org/platon/core/block/GenesisBlock.java new file mode 100644 index 0000000..b32ae6d --- /dev/null +++ b/core/src/main/java/org/platon/core/block/GenesisBlock.java @@ -0,0 +1,46 @@ +package org.platon.core.block; + +import org.platon.common.utils.Numeric; +import org.platon.core.genesis.GenesisJson; +import org.platon.core.Repository; + +import java.math.BigInteger; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * GenesisBlock + * + * @author yanze + * @desc genesis block + * @create 2018-07-31 17:52 + **/ +public class GenesisBlock extends Block { + + private Map accounts = new LinkedHashMap<>(); + + public GenesisBlock(long timestamp, byte[] author, byte[] coinbase, byte[] parentHash, byte[] bloomLog, long number, BigInteger energonUsed, BigInteger energonCeiling, BigInteger difficulty, byte[] extraData) { + super(timestamp, author, coinbase, parentHash, bloomLog, number, energonUsed, energonCeiling, difficulty, extraData); + } + + public GenesisBlock(byte[] encodeBytes) { + super(encodeBytes); + } + + public Map getAccounts() { + return accounts; + } + + public void setAccounts(Map accounts) { + this.accounts = accounts; + } + + public void resolve(Repository repository) { + for (String addrStr : accounts.keySet()) { + byte[] address = Numeric.hexStringToByteArray(addrStr); + BigInteger balance = new BigInteger(accounts.get(addrStr).balance); + repository.createAccount(address); + repository.addBalance(address, balance); + } + } +} diff --git a/core/src/main/java/org/platon/core/codec/DataWordCodec.java b/core/src/main/java/org/platon/core/codec/DataWordCodec.java new file mode 100644 index 0000000..16f89c6 --- /dev/null +++ b/core/src/main/java/org/platon/core/codec/DataWordCodec.java @@ -0,0 +1,32 @@ +package org.platon.core.codec; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.wrapper.DataWord; +import org.platon.core.proto.DataWordMessage; + +public final class DataWordCodec { + + public static DataWordMessage encode(DataWord dataWord) { + if (null == dataWord) { + return null; + } + DataWordMessage.Builder dataWordBuilder = DataWordMessage.newBuilder(); + dataWordBuilder.setData(ByteString.copyFrom(dataWord.getData())); + return dataWordBuilder.build(); + } + + public static DataWord decode(DataWordMessage dataWordMessage) { + return decode(dataWordMessage.toByteArray()); + } + + public static DataWord decode(byte[] data) { + try { + DataWordMessage message = DataWordMessage.parseFrom(data); + return DataWord.of(message.getData().toByteArray()); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/core/src/main/java/org/platon/core/codec/TransactionInfoCodec.java b/core/src/main/java/org/platon/core/codec/TransactionInfoCodec.java new file mode 100644 index 0000000..9b8f52d --- /dev/null +++ b/core/src/main/java/org/platon/core/codec/TransactionInfoCodec.java @@ -0,0 +1,51 @@ +package org.platon.core.codec; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.core.TransactionInfo; +import org.platon.core.proto.TransactionInfoMessage; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.core.transaction.proto.TransactionReceiptProto; + +/** + * @author jungle + * @version 0.0.1 + * @date 2018/9/4 8:41 + */ +public final class TransactionInfoCodec { + + public static TransactionInfoMessage encode(TransactionInfo txInfo) { + if (null == txInfo) { + return null; + } + TransactionInfoMessage.Builder txInfoBuilder = TransactionInfoMessage.newBuilder(); + txInfoBuilder.setBlockHash(ByteString.copyFrom(txInfo.getBlockHash())); + TransactionReceiptProto.TransactionReceiptBase base = TransactionReceiptCodec.encode(txInfo.getReceipt()); + if (base != null) { + txInfoBuilder.setReceipt(base); + } + if (txInfo.getParentBlockHash() != null) { + txInfoBuilder.setParentBlockHash(ByteString.copyFrom(txInfo.getParentBlockHash())); + } + txInfoBuilder.setIndex(txInfo.getIndex()); + return txInfoBuilder.build(); + } + + public static TransactionInfo decode(TransactionInfoMessage txInfoMessage) { + return decode(txInfoMessage.toByteArray()); + } + + public static TransactionInfo decode(byte[] pbTransactionReceipt) { + try { + TransactionInfoMessage txInfoMessage = TransactionInfoMessage.parseFrom(pbTransactionReceipt); + TransactionReceiptProto.TransactionReceiptBase base = txInfoMessage.getReceipt(); + TransactionReceipt txReceipt = TransactionReceiptCodec.decode(base); + + TransactionInfo txInfo = new TransactionInfo(txReceipt, txInfoMessage.getBlockHash().toByteArray(), (int) txInfoMessage.getIndex()); + return txInfo; + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode TransactionInfo exception!"); + } + } +} diff --git a/core/src/main/java/org/platon/core/codec/TransactionReceiptCodec.java b/core/src/main/java/org/platon/core/codec/TransactionReceiptCodec.java new file mode 100644 index 0000000..20b2445 --- /dev/null +++ b/core/src/main/java/org/platon/core/codec/TransactionReceiptCodec.java @@ -0,0 +1,76 @@ +package org.platon.core.codec; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.core.transaction.Bloom; +import org.platon.core.transaction.LogInfo; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.core.transaction.proto.TransactionReceiptProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jungle + * @version 0.0.1 + * @date 2018/9/4 8:41 + */ +public final class TransactionReceiptCodec { + + public static TransactionReceiptProto.TransactionReceiptBase encode(TransactionReceipt txReceipt) { + if (null == txReceipt) { + return null; + } + TransactionReceiptProto.TransactionReceiptBase.Builder txReceiptPb = + TransactionReceiptProto.TransactionReceiptBase.newBuilder(); + + txReceiptPb.setCumulativeEnergon(ByteString.copyFrom(txReceipt.getCumulativeEnergon())); + txReceiptPb.setStateRoot(ByteString.copyFrom(txReceipt.getStateRoot())); + txReceiptPb.setBloomFilter(ByteString.copyFrom(txReceipt.getBloomFilter().getData())); + List logInfoList = txReceipt.getLogInfoList(); + if (logInfoList != null && logInfoList.size() > 0) { + for (int i = 0; i < logInfoList.size(); i++) { + txReceiptPb.addLogs(logInfoList.get(i).getEncodedBuilder()); + } + } + + if (null != txReceipt.getEnergonUsed()) { + txReceiptPb.setEnergonUsed(ByteString.copyFrom(txReceipt.getEnergonUsed())); + } + if (null != txReceipt.getExecutionResult()) { + txReceiptPb.setExecutionResult(ByteString.copyFrom(txReceipt.getExecutionResult())); + } + return txReceiptPb.build(); + } + + public static TransactionReceipt decode(TransactionReceiptProto.TransactionReceiptBase receiptBase) { + return decode(receiptBase.toByteArray()); + } + + public static TransactionReceipt decode(byte[] protoTransactionReceipt) { + TransactionReceipt txReceipt = new TransactionReceipt(); + try { + TransactionReceiptProto.TransactionReceiptBase transactionReceiptBase + = TransactionReceiptProto.TransactionReceiptBase.parseFrom(protoTransactionReceipt); + + txReceipt.setEnergonUsed(transactionReceiptBase.getEnergonUsed().toByteArray()); + txReceipt.setCumulativeEnergon(transactionReceiptBase.getCumulativeEnergon().toByteArray()); + txReceipt.setStateRoot(transactionReceiptBase.getStateRoot().toByteArray()); + + txReceipt.setBloomFilter(new Bloom(transactionReceiptBase.getBloomFilter().toByteArray())); + txReceipt.setExecutionResult(transactionReceiptBase.getExecutionResult().toByteArray()); + + List logInfoList = new ArrayList<>(); + for (int i = 0; i < transactionReceiptBase.getLogsCount(); i++) { + byte[] logs = transactionReceiptBase.getLogs(i).toByteArray(); + LogInfo logInfo = new LogInfo(logs); + logInfoList.add(logInfo); + } + txReceipt.setLogInfoList(logInfoList); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode transactionReceipt exception!"); + } + return txReceipt; + } +} diff --git a/core/src/main/java/org/platon/core/config/BlockchainConfig.java b/core/src/main/java/org/platon/core/config/BlockchainConfig.java new file mode 100644 index 0000000..368270b --- /dev/null +++ b/core/src/main/java/org/platon/core/config/BlockchainConfig.java @@ -0,0 +1,62 @@ +package org.platon.core.config; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.vm.EnergonCost; +import org.platon.core.vm.OpCode; +import org.platon.core.vm.program.Program; + +import java.math.BigInteger; + +/** + * BlockchainConfig + * + * @author yanze + * @desc blockchian config of net and chain info + * @create 2018-07-31 17:01 + **/ +public interface BlockchainConfig { + + /** + * Get blockchain constants + */ + Constants getConstants(); + + /** + * TODO : getConsensus must rewrite so return maybe not String + * @return + */ + String getConsensus(); + + /** + * max energon on block + * @return + */ + BigInteger getEnergonLimit(); + + /** + * interval of producing block + */ + int getBlockProducingInterval(); + + /** + * EVM operations costs + */ + EnergonCost getEnergonCost(); + + /** + * Calculates available energon to be passed for callee + * Since EIP150 + * @param op Opcode + * @param requestedEnergon amount of Energon requested by the program + * @param availableEnergon available Energon + * @throws Program.OutOfEnergonException If passed args doesn't conform to limitations + */ + DataWord getCallEnergon(OpCode op, DataWord requestedEnergon, DataWord availableEnergon) throws Program.OutOfEnergonException; + + /** + * Calculates available energon to be passed for contract constructor + * Since EIP150 + */ + DataWord getCreateEnergon(DataWord availableEnergon); + +} diff --git a/core/src/main/java/org/platon/core/config/BlockchainNetConfig.java b/core/src/main/java/org/platon/core/config/BlockchainNetConfig.java new file mode 100644 index 0000000..7ed6881 --- /dev/null +++ b/core/src/main/java/org/platon/core/config/BlockchainNetConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.config; + +/** + * Describes a set of configs for a specific blockchain depending on the block number + * E.g. the main Ethereum net has at least FrontierConfig and HomesteadConfig depending on the block + * + * Created by Anton Nashatyrev on 25.02.2016. + */ +public interface BlockchainNetConfig { + + /** + * Get the config for the specific block + */ + BlockchainConfig getConfigForBlock(long blockNumber); + + /** + * Returns the constants common for all the blocks in this blockchain + */ + Constants getCommonConstants(); +} diff --git a/core/src/main/java/org/platon/core/config/CommonConfig.java b/core/src/main/java/org/platon/core/config/CommonConfig.java new file mode 100644 index 0000000..ff2694b --- /dev/null +++ b/core/src/main/java/org/platon/core/config/CommonConfig.java @@ -0,0 +1,224 @@ +package org.platon.core.config; + +import org.platon.common.AppenderName; +import org.platon.common.config.IgnoreLoad; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.block.BlockPool; +import org.platon.core.datasource.SourceCodec; +import org.platon.core.db.*; +import org.platon.core.vm.program.ProgramPrecompile; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.*; +import org.platon.storage.datasource.inmemory.HashMapDB; +import org.platon.storage.datasource.leveldb.LevelDBSource; +import org.platon.storage.datasource.rocksdb.RocksDBSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.*; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.util.HashSet; +import java.util.Set; + + +@Configuration +@EnableTransactionManagement +@ComponentScan( + basePackages = "org.platon", + excludeFilters = @ComponentScan.Filter(IgnoreLoad.class)) +public class CommonConfig { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + private Set dbSources = new HashSet<>(); + + private static CommonConfig INSTANCE; + + public static CommonConfig getInstance() { + if (INSTANCE == null) { + INSTANCE = new CommonConfig(); + } + return INSTANCE; + } + + @Bean + public SystemConfig systemConfig() { + return SystemConfig.getSpringDefault(); + } + + @Bean + BeanPostProcessor initializer() { + return new Initializer(); + } + + @Bean + public BlockPool blockPool() { + + return new BlockPool(1024); + } + + + + @Bean @Primary + public Repository repository() { + return new RepositoryWrapper(); + } + + @Bean + public Repository defaultRepository() { + return new RepositoryRoot(stateSource(), null); + } + + @Bean @Scope("prototype") + public Repository repository(byte[] stateRoot) { + return new RepositoryRoot(stateSource(), stateRoot); + } + + @Bean + @Scope("prototype") + public Source cachedDbSource(String name) { + + AbstractCachedSource writeCache = new AsyncWriteCache(blockchainSource(name)) { + @Override + protected WriteCache createCache(Source source) { + WriteCache.BytesKey ret = new WriteCache.BytesKey<>(source, WriteCache.CacheType.SIMPLE); + ret.withSizeEstimators(MemSizeEstimator.ByteArrayEstimator, MemSizeEstimator.ByteArrayEstimator); + ret.setFlushSource(true); + return ret; + } + }.withName(name); + dbFlushManager().addCache(writeCache); + return writeCache; + } + + @Bean + public StateSource stateSource() { + StateSource stateSource = new StateSource(blockchainSource("state"), false); + dbFlushManager().addCache(stateSource.getWriteCache()); + return stateSource; + } + + @Bean + @Scope("prototype") + public Source blockchainSource(String name) { + return new XorDataSource<>(blockchainDbCache(), HashUtil.sha3(name.getBytes())); + } + + @Bean + @Scope("prototype") + protected LevelDBSource levelDbDataSource(String name) { + return new LevelDBSource(name,CoreConfig.getInstance().workDir()); + } + + @Bean + @Scope("prototype") + protected RocksDBSource rocksDbDataSource(String name) { + return new RocksDBSource(name, CoreConfig.getInstance().workDir()); + } + + @Bean + @Scope("prototype") + @Primary + public DbSource keyValueStorage(String name, DbSettings settings) { + String dataSource = CoreConfig.getInstance().getKeyValueDataSource(); + try { + DbSource dbSource; + if ("inmem".equals(dataSource)) { + dbSource = new HashMapDB<>(); + } else if ("leveldb".equals(dataSource)){ + dbSource = levelDbDataSource(name); + } else { + dataSource = "rocksdb"; + dbSource = rocksDbDataSource(name); + } + dbSource.open(settings); + dbSources.add(dbSource); + return dbSource; + } finally { + logger.info(dataSource + " key-value data source created: " + name); + } + } + + @Bean + public DbSource blockchainDB() { + + DbSettings settings = DbSettings.newInstance() + .withMaxOpenFiles(512) + .withMaxThreads(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + return keyValueStorage("blockchain", settings); + } + + @Bean + public AbstractCachedSource blockchainDbCache() { + + WriteCache.BytesKey ret = new WriteCache.BytesKey<>( + new BatchSourceWriter<>(blockchainDB()), WriteCache.CacheType.SIMPLE); + ret.setFlushSource(true); + + return ret; + } + + @Bean + public CoreConfig config(){ + return CoreConfig.getInstance(); + } + + @Bean + public DbFlushManager dbFlushManager() { + return new DbFlushManager(config(), dbSources, blockchainDbCache()); + } + + @Bean + @Lazy + public DbSource headerStorage() { + return keyValueStorage("headers",DbSettings.DEFAULT); + } + + @Bean + @Lazy + public HeaderStore headerStore() { + + DbSource dataStorage = headerStorage(); + + WriteCache.BytesKey cache = new WriteCache.BytesKey<>( + new BatchSourceWriter<>(dataStorage), WriteCache.CacheType.SIMPLE); + cache.setFlushSource(true); + + + dbFlushManager().addCache(cache); + + HeaderStore headerStore = new HeaderStore(); + Source headers = new XorDataSource<>(cache, HashUtil.sha3("header".getBytes())); + Source index = new XorDataSource<>(cache, HashUtil.sha3("index".getBytes())); + headerStore.init(index, headers); + + return headerStore; + } + + @Bean + public Source precompileSource() { + + StateSource source = stateSource(); + return new SourceCodec(source, + new SerializerIfc() { + public byte[] serialize(byte[] object) { + DataWord ret = DataWord.of(object); + ret.add(DataWord.of(1)); + return ret.getLast20Bytes(); + } + public byte[] deserialize(byte[] stream) { + throw new RuntimeException("Shouldn't be called"); + } + }, new SerializerIfc() { + public byte[] serialize(ProgramPrecompile object) { + return object == null ? null : object.serialize(); + } + public ProgramPrecompile deserialize(byte[] stream) { + return stream == null ? null : ProgramPrecompile.deserialize(stream); + } + }); + } + +} diff --git a/core/src/main/java/org/platon/core/config/Constants.java b/core/src/main/java/org/platon/core/config/Constants.java new file mode 100644 index 0000000..12ea8f0 --- /dev/null +++ b/core/src/main/java/org/platon/core/config/Constants.java @@ -0,0 +1,30 @@ +package org.platon.core.config; + +/** + * Describes different constants + */ +public class Constants { + + public static final int PERMISSION_TRIE_THRESHOLD = 1024; + + public static final String DATABASE_SOURCE_LEVELDB = "leveldb"; + public static final String DATABASE_SOURCE_ROCKSDB = "rocksdb"; + + public static final int ERROR__1001_UNLOCK_ACCOUNT = 1001; + public static final int ERROR__1002_KEY_NOT_FOUND = 1002; + + /** + * New DELEGATECALL opcode introduced in the Homestead release. Before Homestead this opcode should generate + * exception + */ + public boolean hasDelegateCallOpcode() {return false; } + + /** + * Introduced in the Homestead release + */ + public boolean createEmptyContractOnOOG() { + return true; + } + + public int getMAX_CONTRACT_SZIE() { return Integer.MAX_VALUE; } +} diff --git a/core/src/main/java/org/platon/core/config/CoreConfig.java b/core/src/main/java/org/platon/core/config/CoreConfig.java new file mode 100644 index 0000000..68ba660 --- /dev/null +++ b/core/src/main/java/org/platon/core/config/CoreConfig.java @@ -0,0 +1,204 @@ +package org.platon.core.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.platon.common.config.Validated; +import org.platon.common.config.ConfigProperties; +import org.platon.common.utils.Numeric; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +public class CoreConfig { + + private Config config; + + public final static String PROPERTY_DB_DIR = "database.dir"; + public final static String PROPERTY_DB_RESET = "database.reset"; + private final static String DEFAULT_CONFIG_KEY = "core"; + + private Boolean vmTrace; + private Boolean recordInternalTransactionsData; + + public static CoreConfig getInstance() { + return new CoreConfig(ConfigProperties.getInstance().getConfig().getObject(DEFAULT_CONFIG_KEY).toConfig()); + } + + public void overrideParams(Config overrideOptions) { + config = overrideOptions.withFallback(config); + validateConfig(); + } + + public void overrideParams(String... keyValuePairs) { + if (keyValuePairs.length % 2 != 0) throw new RuntimeException("Odd argument number"); + Map map = new HashMap<>(); + for (int i = 0; i < keyValuePairs.length; i += 2) { + map.put(keyValuePairs[i], keyValuePairs[i + 1]); + } + overrideParams(map); + } + + public void overrideParams(Map cliOptions) { + Config cliConf = ConfigFactory.parseMap(cliOptions); + overrideParams(cliConf); + } + + public CoreConfig(Config config) { + this.config = config; + } + + public T getProperty(String propName, T defaultValue) { + if (!config.hasPath(propName)) return defaultValue; + String string = config.getString(propName); + if (string.trim().isEmpty()) return defaultValue; + return (T) config.getAnyRef(propName); + } + + private void validateConfig() { + for (Method method : getClass().getMethods()) { + try { + if (method.isAnnotationPresent(Validated.class)) { + method.invoke(this); + } + } catch (Exception e) { + throw new RuntimeException("Error validating config method: " + method, e); + } + } + } + + public String workDir() { + return System.getProperty("user.dir"); + } + + @Validated + public String keystoreDir() { + return config.getString("keystore.dir"); + } + + @Validated + public String genesisFileName() { + return config.getString("genesis.resourcePath"); + } + + @Validated + public String ancestorBlockCacheSize() { + return config.getString("validator.timeliness.ancestorBlockCache.size"); + } + + @Validated + public String pendingBlockCacheSize() { + return config.getString("validator.timeliness.pendingBlockCache.size"); + } + + @Validated + public String pendingTxCacheSize() { + return config.getString("validator.timeliness.pendingTxCache.size"); + } + + @Validated + public byte[] getMinerCoinbase() { + String sc = config.getString("mine.coinbase"); + if (StringUtils.isEmpty(sc)) { + throw new RuntimeException("miner.coinbase has invalid length: '" + sc + "'"); + } + byte[] c = Numeric.hexStringToByteArray(sc); + return c; + } + + @Validated + public long getMineBlockTime() { + String stime = config.getString("mine.mineBlockTime"); + if (null == stime || "".equals(stime)) { + return 1000; + } + return Long.valueOf(stime); + } + + @Validated + public String databaseDir() { + return config.getString("database.dir"); + } + + @Validated + public String databaseSource() { + return config.getString("database.source"); + } + + public Integer databaseVersion() { + return config.getInt("database.version"); + } + + public boolean databaseReset() { + return config.getBoolean("database.reset"); + } + + public long databaseResetBlock() { + return config.getLong("database.resetBlock"); + } + + @Validated + public String getKeyValueDataSource() { + return config.getString("database.source"); + } + + @Validated + public String getIncompatibleDatabaseBehavior() { + return config.getString("database.incompatibleDatabaseBehavior"); + } + + @Validated + public boolean blockChainOnly() { + String key = "chain.only"; + if (!config.hasPath(key)) return false; + return config.getBoolean(key); + } + + public boolean isRecordBlocks() { + String key = "block.record"; + if (!config.hasPath(key)) return false; + return config.getBoolean(key); + } + + public String dumpDir() { + return config.getString("dump.dir"); + } + + public BigInteger getMineMinEnergonPrice() { + return new BigInteger(config.getString("mine.minEnergonPrice")); + } + + @Validated + public boolean vmTrace() { + return vmTrace == null ? (vmTrace = config.getBoolean("vm.structured.trace")) : vmTrace; + } + + @Validated + public boolean recordInternalTransactionsData() { + if (recordInternalTransactionsData == null) { + recordInternalTransactionsData = config.getBoolean("record.internal.transactions.data"); + } + return recordInternalTransactionsData; + } + + @Validated + public int dumpBlock() { + return config.getInt("dump.block"); + } + + @Validated + public String dumpStyle() { + return config.getString("dump.style"); + } + + public void setRecordInternalTransactionsData(Boolean recordInternalTransactionsData) { + this.recordInternalTransactionsData = recordInternalTransactionsData; + } + + public Config getConfig() { + return this.config; + } + +} diff --git a/core/src/main/java/org/platon/core/config/DBVersionProcessor.java b/core/src/main/java/org/platon/core/config/DBVersionProcessor.java new file mode 100644 index 0000000..9a9d7e9 --- /dev/null +++ b/core/src/main/java/org/platon/core/config/DBVersionProcessor.java @@ -0,0 +1,118 @@ +package org.platon.core.config; + +import org.platon.common.AppenderName; +import org.platon.common.utils.FileUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.Properties; + + +public class DBVersionProcessor { + + private static final Logger logger = LoggerFactory.getLogger(DBVersionProcessor.class); + + public enum Behavior { + EXIT, RESET, IGNORE + } + + public void process(CoreConfig config) { + + String dbType = config.getKeyValueDataSource(); + if("leveldb".equals(dbType)){ + AppenderName.showWarn( + "Deprecated database engine detected.", + "'leveldb' support will be removed in one of the next releases.", + "thus it's strongly recommended to stick with 'rocksdb' instead."); + } + boolean reset = config.databaseReset(); + long resetBlock = config.databaseResetBlock(); + if(reset && 0 == resetBlock){ + FileUtil.recursiveDelete(config.databaseDir()); + putDatabaseVersion(config,config.databaseVersion()); + logger.info("To reset database success."); + } + + final File versionFile = getDatabaseVersionFile(config); + final Behavior behavior = Behavior.valueOf( + config.getIncompatibleDatabaseBehavior()==null ? Behavior.EXIT.toString() : config.getIncompatibleDatabaseBehavior().toUpperCase() + ); + + + final Integer expectedVersion = config.databaseVersion(); + if(isDatabaseDirectoryExists(config)){ + final Integer actualVersionRaw = getDatabaseVersion(versionFile); + final boolean isVersionFileNotFound = (Integer.valueOf(-1)).equals(actualVersionRaw); + final Integer actualVersion = isVersionFileNotFound ? 1 : actualVersionRaw; + if(actualVersionRaw.equals(-1)){ + putDatabaseVersion(config,actualVersion); + } + + if(actualVersion.equals(expectedVersion) || (isVersionFileNotFound && expectedVersion.equals(1))){ + logger.info("Database directory location: '{}', version: {}", config.databaseDir(), actualVersion); + } else { + + logger.warn("Detected incompatible database version. Detected:{}, required:{}", actualVersion, expectedVersion); + if(behavior == Behavior.EXIT){ + AppenderName.showErrorAndExit( + "Incompatible database version " + actualVersion, + "Please remove database directory manually or set `database.incompatibleDatabaseBehavior` to `RESET`", + "Database directory location is " + config.databaseDir() + ); + }else if(behavior == Behavior.RESET){ + boolean res = FileUtil.recursiveDelete(config.databaseDir()); + if(!res){ + throw new RuntimeException("Couldn't delete database dir : " + config.databaseDir()); + } + putDatabaseVersion(config,config.databaseVersion()); + logger.warn("Auto reset database directory according to flag."); + }else { + logger.info("Continue working according to flag."); + } + } + + } else{ + putDatabaseVersion(config,config.databaseVersion()); + logger.info("Created database version file done."); + } + } + + public boolean isDatabaseDirectoryExists(CoreConfig config) { + final File databaseFile = new File(config.databaseDir()); + return databaseFile.exists() && databaseFile.isDirectory() && databaseFile.list().length > 0; + } + + + public Integer getDatabaseVersion(File file) { + if (!file.exists()) { + return -1; + } + try (Reader reader = new FileReader(file)) { + Properties prop = new Properties(); + prop.load(reader); + return Integer.valueOf(prop.getProperty("databaseVersion")); + } catch (Exception e) { + logger.error("Problem reading current database version.", e); + return -1; + } + } + + + public void putDatabaseVersion(CoreConfig config, Integer version) { + final File versionFile = getDatabaseVersionFile(config); + versionFile.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(versionFile)) { + Properties prop = new Properties(); + prop.setProperty("databaseVersion", version.toString()); + prop.store(writer, "Generated database version"); + } catch (Exception e) { + throw new Error("Problem writing current database version ", e); + } + } + + + private File getDatabaseVersionFile(CoreConfig config) { + return new File(config.databaseDir() + "/version.properties"); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/config/DefaultConfig.java b/core/src/main/java/org/platon/core/config/DefaultConfig.java new file mode 100644 index 0000000..517bc8d --- /dev/null +++ b/core/src/main/java/org/platon/core/config/DefaultConfig.java @@ -0,0 +1,49 @@ +package org.platon.core.config; + +import org.platon.common.AppenderName; +import org.platon.core.datasource.TransactionStore; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.db.BlockStoreImpl; +import org.platon.storage.datasource.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(CommonConfig.class) +public class DefaultConfig { + + private static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + @Autowired + CommonConfig commonConfig; + + @Autowired + ApplicationContext appCtx; + + public DefaultConfig() { + Thread.setDefaultUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception", e)); + } + + public ApplicationContext getAppCtx(){ + return appCtx; + } + + @Bean + public BlockStoreIfc blockStore(){ + BlockStoreImpl indexedBlockStore = new BlockStoreImpl(); + Source block = commonConfig.cachedDbSource("block"); + Source index = commonConfig.cachedDbSource("index"); + indexedBlockStore.init(index, block); + return indexedBlockStore; + } + + @Bean + public TransactionStore transactionStore() { + return new TransactionStore(commonConfig.cachedDbSource("transactions")); + } +} diff --git a/core/src/main/java/org/platon/core/config/Initializer.java b/core/src/main/java/org/platon/core/config/Initializer.java new file mode 100644 index 0000000..c906a91 --- /dev/null +++ b/core/src/main/java/org/platon/core/config/Initializer.java @@ -0,0 +1,37 @@ +package org.platon.core.config; + +import org.platon.common.AppenderName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +class Initializer implements BeanPostProcessor { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + private void initConfig(SystemConfig config) { + + logger.info("~> initConfig(SystemConfig config)..."); + + //loading blockchain config + config.getBlockchainConfig(); + + //todo: 测试屏蔽 + //loading genesis config + //config.getGenesisBlock(); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof SystemConfig) { + initConfig((SystemConfig) bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } +} diff --git a/core/src/main/java/org/platon/core/config/SystemConfig.java b/core/src/main/java/org/platon/core/config/SystemConfig.java new file mode 100644 index 0000000..f23210c --- /dev/null +++ b/core/src/main/java/org/platon/core/config/SystemConfig.java @@ -0,0 +1,82 @@ +package org.platon.core.config; + +import org.platon.core.Repository; +import org.platon.core.block.GenesisBlock; +import org.platon.core.genesis.GenesisJson; +import org.platon.core.genesis.GenesisLoader; + +/** + * SystemConfig + * + * @author yanze + * @desc loading system config + * @create 2018-07-31 16:58 + */ +public class SystemConfig { + + private BlockchainNetConfig blockchainConfig; + private GenesisJson genesisJson; + private GenesisBlock genesisBlock; + private static SystemConfig systemConfig; + private static boolean useOnlySpringConfig = false; + + private CoreConfig config; + + /** + * Returns the static config instance. If the config is passed + * as a Spring bean by the application this instance shouldn't + * be used + * This method is mainly used for testing purposes + * (Autowired fields are initialized with this static instance + * but when running within Spring context they replaced with the + * bean config instance) + */ + public static SystemConfig getDefault() { + return useOnlySpringConfig ? null : getSpringDefault(); + } + + public static SystemConfig getSpringDefault() { + if (systemConfig == null) { + systemConfig = new SystemConfig(); + } + return systemConfig; + } + + public BlockchainNetConfig getBlockchainConfig(){ + if(blockchainConfig == null){ + //loading blockchain config + GenesisJson genesisJson = getGenesisJson(); + if(genesisJson.getJsonBlockchainConfig() != null){ + //TODO blockchainConfig or BlockchainNetConfig +// blockchainConfig = genesisJson.getJsonBlockchainConfig(); + }else{ + //TODO default config and add another config + throw new RuntimeException("system config is null"); + } + } + return blockchainConfig; + } + + public GenesisJson getGenesisJson() { + if(genesisJson == null){ + genesisJson = GenesisLoader.loadGenesisJson(CoreConfig.getInstance().genesisFileName()); + } + return genesisJson; + } + + public GenesisBlock getGenesisBlock(Repository repository) { + if (genesisBlock == null) { + genesisBlock = GenesisLoader.loadGenesisBlock(repository, getGenesisJson()); + } + return genesisBlock; + } + + static boolean isUseOnlySpringConfig() { + return useOnlySpringConfig; + } + + public static void setUseOnlySpringConfig(boolean useOnlySpringConfig) { + SystemConfig.useOnlySpringConfig = useOnlySpringConfig; + } + +} diff --git a/core/src/main/java/org/platon/core/config/net/BaseNetConfig.java b/core/src/main/java/org/platon/core/config/net/BaseNetConfig.java new file mode 100644 index 0000000..210320f --- /dev/null +++ b/core/src/main/java/org/platon/core/config/net/BaseNetConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.config.net; + +import org.platon.core.config.BlockchainConfig; +import org.platon.core.config.BlockchainNetConfig; +import org.platon.core.config.Constants; + +import java.util.Arrays; + +/** + * Created by Anton Nashatyrev on 25.02.2016. + */ +public class BaseNetConfig implements BlockchainNetConfig { + private long[] blockNumbers = new long[64]; + private BlockchainConfig[] configs = new BlockchainConfig[64]; + private int count; + + public void add(long startBlockNumber, BlockchainConfig config) { + if (count >= blockNumbers.length) throw new RuntimeException(); + if (count > 0 && blockNumbers[count] >= startBlockNumber) + throw new RuntimeException("Block numbers should increase"); + if (count == 0 && startBlockNumber > 0) throw new RuntimeException("First config should start from block 0"); + blockNumbers[count] = startBlockNumber; + configs[count] = config; + count++; + } + + @Override + public BlockchainConfig getConfigForBlock(long blockNumber) { + for (int i = 0; i < count; i++) { + if (blockNumber < blockNumbers[i]) return configs[i - 1]; + } + return configs[count - 1]; + } + + @Override + public Constants getCommonConstants() { + // TODO make a guard wrapper which throws exception if the requested constant differs among configs + return configs[0].getConstants(); + } + + @Override + public String toString() { + return "BaseNetConfig{" + + "blockNumbers=" + Arrays.toString(Arrays.copyOfRange(blockNumbers, 0, count)) + + ", configs=" + Arrays.toString(Arrays.copyOfRange(configs, 0, count)) + + ", count=" + count + + '}'; + } +} diff --git a/core/src/main/java/org/platon/core/consensus/BaseConsensus.java b/core/src/main/java/org/platon/core/consensus/BaseConsensus.java new file mode 100644 index 0000000..f9b6a3a --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/BaseConsensus.java @@ -0,0 +1,97 @@ +package org.platon.core.consensus; + +import org.platon.common.utils.SpringContextUtil; +import org.platon.core.Worker; +import org.platon.core.block.Block; +import org.platon.core.block.BlockHeader; +import org.platon.core.block.BlockPool; +import org.platon.core.config.CommonConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by alliswell on 2018/7/25. + */ + +public abstract class BaseConsensus extends Worker { + + private static final Logger logger = LoggerFactory.getLogger(BaseConsensus.class); + protected BlockPool blockPool; + private CommonConfig commonConfig; + + public BaseConsensus(BlockPool blockPool) { + this.blockPool = blockPool; + } + + public abstract boolean shouldSeal(); + + public abstract byte[] headHash(); + public abstract long headNumber(); + + public abstract byte[] currentIrbHash(); + + public abstract long currentIrbNumber(); + + public abstract byte[] lastIrbHash(); + + public abstract long lastIrbNumber(); + + public abstract void generateSeal(BlockHeader header); + + /** + * Determines whether the block should be written to db + * + * @param blockHash block's hash + * @return + */ + public abstract boolean shouldCommit(byte[] blockHash); + + /** + * process the raw block + * + * @param block + */ + protected abstract void processRawBlock(Block block); + + /** + * mining + */ + protected abstract void mine(); + + @Override + protected void beforeWorking() { + commonConfig = (CommonConfig) SpringContextUtil.getBean(CommonConfig.class); + setIdleInMills(20); + } + + @Override + public void doWork() { + + if (null == blockPool) { + logger.error("the blockPool is null!"); + return; + } + + while (blockPool.hasRawBlocks()) { + Block block = blockPool.pollRawBlock(); + processRawBlock(block); + } + mine(); + + try { + synchronized (blockPool) { + if (!blockPool.hasRawBlocks()) { + logger.debug("there is no raw blocks in queue!"); + blockPool.wait(); + } + } + + } catch (InterruptedException ie) { + ie.printStackTrace(); + logger.error(ie.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + logger.error(e.getMessage()); + } + } +} diff --git a/core/src/main/java/org/platon/core/consensus/BftWorker.java b/core/src/main/java/org/platon/core/consensus/BftWorker.java new file mode 100644 index 0000000..44784a9 --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/BftWorker.java @@ -0,0 +1,106 @@ +package org.platon.core.consensus; + +import org.platon.common.utils.ByteUtil; +import org.platon.core.block.Block; +import org.platon.core.block.BlockHeader; +import org.platon.core.block.BlockPool; +import org.platon.p2p.router.MessageRouter; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Vector; + +/** + * Created by alliswell on 2018/7/25. + */ +public class BftWorker extends BaseConsensus { + + @Autowired + MessageRouter messageRouter; + + private DposFace dpos; + private byte[] myAddress; + + public BftWorker(BlockPool blockPool, byte[] minerAddr) { + super(blockPool); + dpos = new DposFace(); + myAddress = minerAddr; + } + + private boolean iAmPrimary() { + return dpos.isPrimary(myAddress); + } + + @Override + protected void beforeWorking() { + super.beforeWorking(); + } + + @Override + public boolean shouldSeal() { + + if (!iAmPrimary()) { + return false; + } + + long now = System.currentTimeMillis(); + Vector primaryNodes = dpos.getCurrentPrimary(); + + primaryNodes.forEach(node -> { + + }); + ByteUtil.contains(dpos.getCurrentPrimary(), myAddress); + return false; + } + + @Override + public byte[] headHash() { + return new byte[0]; + } + + @Override + public long headNumber() { + return 0; + } + + @Override + public byte[] currentIrbHash() { + return new byte[0]; + } + + @Override + public long currentIrbNumber() { + return 0; + } + + @Override + public byte[] lastIrbHash() { + return new byte[0]; + } + + @Override + public long lastIrbNumber() { + return 0; + } + + @Override + public void generateSeal(BlockHeader header) { + + } + + @Override + public boolean shouldCommit(byte[] blockHash) { + int sigsCount = blockPool.getSignaturesCount(blockHash); + int requiredSigsCount = dpos.getCurrentPrimary().size() * 2 / 3 + 1; + return sigsCount >= requiredSigsCount; + } + + @Override + protected void processRawBlock(Block block) { + + } + + @Override + protected void mine() { + + } +} diff --git a/core/src/main/java/org/platon/core/consensus/ConsensusManager.java b/core/src/main/java/org/platon/core/consensus/ConsensusManager.java new file mode 100644 index 0000000..10de6a4 --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/ConsensusManager.java @@ -0,0 +1,91 @@ +package org.platon.core.consensus; + +import com.google.common.util.concurrent.ListenableFuture; +import org.platon.common.AppenderName; +import org.platon.core.block.Block; +import org.platon.core.block.BlockHeader; +import org.platon.core.config.CoreConfig; +import org.platon.core.mine.MinerAlgorithmIfc; +import org.platon.core.mine.MinerListener; +import org.platon.core.mine.MiningResult; +import org.platon.core.mine.NoMinerAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Component +public class ConsensusManager { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_CONSENSUS); + + private final CoreConfig config; + + private MinerAlgorithmIfc minerAlgorithm; + + private Thread consensusThread; + + @Autowired + public ConsensusManager(CoreConfig config){ + this.config = config; + this.minerAlgorithm = getMinerAlgorithm(config); + Runnable consensusTask = this::processConsensus; + consensusThread = new Thread(consensusTask); + consensusThread.start(); + } + + private void processConsensus(){ + if (logger.isDebugEnabled()) { + logger.debug("~> process consensus thread start."); + } + while(!Thread.currentThread().isInterrupted()){ + + try { + + }catch (Exception e){ + if(e instanceof InterruptedException){ + break; + } + logger.error("~> Error process consensus.", e); + } + } + } + + public ListenableFuture mine(Block block) { + return this.minerAlgorithm.mine(block); + } + + public boolean validate(BlockHeader blockHeader) { + return this.minerAlgorithm.validate(blockHeader); + } + + public void setListeners(Collection listeners) { + this.minerAlgorithm.setListeners(listeners); + } + + public boolean shouldSeal() { + return this.minerAlgorithm.shouldSeal(); + } + + public byte[] headHash() { + return this.minerAlgorithm.headHash(); + } + + public byte[] currentIrbHash() { + return this.minerAlgorithm.currentIrbHash(); + } + + public byte[] lastIrbHash() { + return this.minerAlgorithm.lastIrbHash(); + } + + public void generateSeal(BlockHeader header) { + this.minerAlgorithm.generateSeal(header); + } + + private MinerAlgorithmIfc getMinerAlgorithm(CoreConfig config){ + return new NoMinerAlgorithm(); + } +} diff --git a/core/src/main/java/org/platon/core/consensus/DposFace.java b/core/src/main/java/org/platon/core/consensus/DposFace.java new file mode 100644 index 0000000..17f34e3 --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/DposFace.java @@ -0,0 +1,49 @@ +package org.platon.core.consensus; + +import org.platon.common.utils.ByteUtil; + +import java.util.Vector; + +/** + * dpos related + * + * @author alliswell + * @version 0.0.1 + * @date 2018/8/28 13:42 + */ +public class DposFace { + + /** + * primary node's address list + */ + private Vector primaryList; + /** + * block number of last consensus cycle + */ + private long lastCycleBlockNum = 0L; + + public DposFace() { + this.primaryList = new Vector<>(); + } + + public long getLastCycleBlockNum() { + return lastCycleBlockNum; + } + + public void setLastCycleBlockNum(long lastCycleBlockNum) { + this.lastCycleBlockNum = lastCycleBlockNum; + } + + /** + * TODO get the current primary node's address list + * + * @return + */ + public Vector getCurrentPrimary() { + return this.primaryList; + } + + public boolean isPrimary(byte[] address) { + return ByteUtil.contains(primaryList, address); + } +} diff --git a/core/src/main/java/org/platon/core/consensus/NoProofWorker.java b/core/src/main/java/org/platon/core/consensus/NoProofWorker.java new file mode 100644 index 0000000..63c5f4b --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/NoProofWorker.java @@ -0,0 +1,70 @@ +package org.platon.core.consensus; + +import org.platon.core.block.Block; +import org.platon.core.block.BlockHeader; +import org.platon.core.block.BlockPool; + +/** + * Created by alliswell on 2018/7/25. + */ +public class NoProofWorker extends BaseConsensus { + + public NoProofWorker(BlockPool blockPool) { + super(blockPool); + } + + @Override + public boolean shouldSeal() { + return true; + } + + @Override + public byte[] headHash() { + return new byte[0]; + } + + @Override + public long headNumber() { + return 0; + } + + @Override + public byte[] currentIrbHash() { + return new byte[0]; + } + + @Override + public long currentIrbNumber() { + return 0; + } + + @Override + public byte[] lastIrbHash() { + return new byte[0]; + } + + @Override + public long lastIrbNumber() { + return 0; + } + + @Override + public void generateSeal(BlockHeader header) { + System.out.println("controller submit a block, hash=" + header.getHash()); + } + + @Override + public boolean shouldCommit(byte[] blockHash) { + return true; + } + + @Override + protected void processRawBlock(Block block) { + + } + + @Override + protected void mine() { + + } +} diff --git a/core/src/main/java/org/platon/core/consensus/PrimaryOrderComparator.java b/core/src/main/java/org/platon/core/consensus/PrimaryOrderComparator.java new file mode 100644 index 0000000..9c1303b --- /dev/null +++ b/core/src/main/java/org/platon/core/consensus/PrimaryOrderComparator.java @@ -0,0 +1,29 @@ +package org.platon.core.consensus; + +import java.math.BigInteger; + +/** + * compare primary node's address + * + * @author alliswell + * @version 0.0.1 + * @date 2018/8/28 19:56 + */ +public class PrimaryOrderComparator implements java.util.Comparator { + + /** + * seed of comparator + */ + private byte[] seed; + + public PrimaryOrderComparator(byte[] seed) { + this.seed = seed; + } + + @Override + public int compare(byte[] n1, byte[] n2) { + BigInteger first = new BigInteger(n1).xor(new BigInteger(seed)); + BigInteger second = new BigInteger(n2).xor(new BigInteger(seed)); + return first.subtract(second).intValue(); + } +} diff --git a/core/src/main/java/org/platon/core/datasource/DataSourceArray.java b/core/src/main/java/org/platon/core/datasource/DataSourceArray.java new file mode 100644 index 0000000..a42b553 --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/DataSourceArray.java @@ -0,0 +1,60 @@ +package org.platon.core.datasource; + +import org.platon.common.utils.ByteUtil; +import org.spongycastle.util.encoders.Hex; + +import java.util.AbstractList; + +public class DataSourceArray extends AbstractList { + + private ObjectDataSource src; + private static final byte[] SIZE_KEY = Hex.decode("FFFFFFFFFFFFFFFF"); + private int size = -1; + + public DataSourceArray(ObjectDataSource src) { + this.src = src; + } + + public synchronized boolean flush() { + return src.flush(); + } + + @Override + public synchronized V set(int idx, V value) { + if (idx >= size()) { + setSize(idx + 1); + } + src.put(ByteUtil.intToBytes(idx), value); + return value; + } + + @Override + public synchronized void add(int index, V element) { + set(index, element); + } + + @Override + public synchronized V remove(int index) { + throw new RuntimeException("Not supported yet."); + } + + @Override + public synchronized V get(int idx) { + if (idx < 0 || idx >= size()) throw new IndexOutOfBoundsException(idx + " > " + size); + return src.get(ByteUtil.intToBytes(idx)); + } + + @Override + public synchronized int size() { + if (size < 0) { + byte[] sizeBB = src.getSource().get(SIZE_KEY); + size = sizeBB == null ? 0 : ByteUtil.byteArrayToInt(sizeBB); + } + return size; + } + + private synchronized void setSize(int newSize) { + size = newSize; + src.getSource().put(SIZE_KEY, ByteUtil.intToBytes(newSize)); + } +} diff --git a/core/src/main/java/org/platon/core/datasource/JournalSource.java b/core/src/main/java/org/platon/core/datasource/JournalSource.java new file mode 100644 index 0000000..7f377ab --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/JournalSource.java @@ -0,0 +1,135 @@ +package org.platon.core.datasource; + +import org.platon.storage.datasource.AbstractChainedSource; +import org.platon.storage.datasource.HashedKeySource; +import org.platon.storage.datasource.SerializerIfc; +import org.platon.storage.datasource.Source; +import org.platon.storage.datasource.inmemory.HashMapDB; + +import java.util.ArrayList; +import java.util.List; + +public class JournalSource extends AbstractChainedSource + implements HashedKeySource { + + public static class Update { + byte[] updateHash; + List insertedKeys = new ArrayList<>(); + List deletedKeys = new ArrayList<>(); + + public Update() { + } + + public Update(byte[] bytes) { + parse(bytes); + } + + public byte[] serialize() { + /*byte[][] insertedBytes = new byte[insertedKeys.size()][]; + for (int i = 0; i < insertedBytes.length; i++) { + insertedBytes[i] = RLP.encodeElement(insertedKeys.get(i)); + } + byte[][] deletedBytes = new byte[deletedKeys.size()][]; + for (int i = 0; i < deletedBytes.length; i++) { + deletedBytes[i] = RLP.encodeElement(deletedKeys.get(i)); + } + return RLP.encodeList(RLP.encodeElement(updateHash), + RLP.encodeList(insertedBytes), RLP.encodeList(deletedBytes));*/ + return null; + } + + private void parse(byte[] encoded) { + /*RLPList l = (RLPList) RLP.decode2(encoded).get(0); + updateHash = l.get(0).getRLPData(); + + for (RLPElement aRInserted : (RLPList) l.get(1)) { + insertedKeys.add(aRInserted.getRLPData()); + } + for (RLPElement aRDeleted : (RLPList) l.get(2)) { + deletedKeys.add(aRDeleted.getRLPData()); + }*/ + } + + public List getInsertedKeys() { + return insertedKeys; + } + + public List getDeletedKeys() { + return deletedKeys; + } + } + + private Update currentUpdate = new Update(); + + Source journal = new HashMapDB<>(); + + /** + * Constructs instance with the underlying backing Source + */ + public JournalSource(Source src) { + super(src); + } + + public void setJournalStore(Source journalSource) { + journal = new SourceCodec.BytesKey<>(journalSource, + new SerializerIfc() { + public byte[] serialize(Update object) { + return object.serialize(); + } + + public Update deserialize(byte[] stream) { + return stream == null ? null : new Update(stream); + } + }); + } + + @Override + public synchronized void put(byte[] key, V val) { + if (val == null) { + delete(key); + return; + } + + getSource().put(key, val); + currentUpdate.insertedKeys.add(key); + } + + /** + * Deletes are not propagated to the backing Source immediately + * but instead they are recorded to the current Update and + * might be later persisted + */ + @Override + public synchronized void delete(byte[] key) { + currentUpdate.deletedKeys.add(key); + } + + @Override + public synchronized V get(byte[] key) { + return getSource().get(key); + } + + /** + * Records all the changes made prior to this call to a single chunk + * with supplied hash. + * Later those updates could be either persisted to backing Source (deletes only) + * or reverted from the backing Source (inserts only) + */ + public synchronized Update commitUpdates(byte[] updateHash) { + currentUpdate.updateHash = updateHash; + journal.put(updateHash, currentUpdate); + Update committed = currentUpdate; + currentUpdate = new Update(); + return committed; + } + + public Source getJournal() { + return journal; + } + + @Override + public synchronized boolean flushImpl() { + journal.flush(); + return false; + } +} diff --git a/core/src/main/java/org/platon/core/datasource/ObjectDataSource.java b/core/src/main/java/org/platon/core/datasource/ObjectDataSource.java new file mode 100644 index 0000000..6d6470d --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/ObjectDataSource.java @@ -0,0 +1,24 @@ +package org.platon.core.datasource; + +import org.platon.storage.datasource.ReadCache; +import org.platon.storage.datasource.SerializerIfc; +import org.platon.storage.datasource.Source; +import org.platon.storage.datasource.SourceChainBox; + +public class ObjectDataSource extends SourceChainBox { + + private ReadCache cache; + private SourceCodec codec; + private Source byteSource; + + public ObjectDataSource(Source byteSource, + SerializerIfc serializer, + int readCacheEntries) { + super(byteSource); + this.byteSource = byteSource; + add(codec = new SourceCodec<>(byteSource, new Serializers.Identity(), serializer)); + if (readCacheEntries > 0) { + add(cache = new ReadCache.BytesKey<>(codec).withMaxCapacity(readCacheEntries)); + } + } +} diff --git a/core/src/main/java/org/platon/core/datasource/Serializers.java b/core/src/main/java/org/platon/core/datasource/Serializers.java new file mode 100644 index 0000000..5bd03a7 --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/Serializers.java @@ -0,0 +1,102 @@ +package org.platon.core.datasource; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.block.BlockHeader; +import org.platon.core.codec.DataWordCodec; +import org.platon.storage.datasource.SerializerIfc; + +public class Serializers { + + /** + * No conversion + */ + public static class Identity implements SerializerIfc { + @Override + public T serialize(T object) { + return object; + } + @Override + public T deserialize(T stream) { + return stream; + } + } + + public final static SerializerIfc AccountStateSerializer = new SerializerIfc() { + @Override + public byte[] serialize(Account object) { + return object.getEncoded(); + } + + @Override + public Account deserialize(byte[] stream) { + return stream == null || stream.length == 0 ? null : new Account(stream); + } + }; + + public final static SerializerIfc StorageKeySerializer = new SerializerIfc() { + @Override + public byte[] serialize(DataWord object) { + return object.getData(); + } + + @Override + public DataWord deserialize(byte[] stream) { + return DataWord.of(stream); + } + }; + + public final static SerializerIfc StorageValueSerializer = new SerializerIfc() { + + @Override + public byte[] serialize(DataWord object) { + return DataWordCodec.encode(object).toByteArray(); + } + + @Override + public DataWord deserialize(byte[] stream) { + if (stream == null || stream.length == 0) return null; + return DataWordCodec.decode(stream); + } + }; + + /*public final static SerializerIfc TrieNodeSerializer = new SerializerIfc() { + @Override + public byte[] serialize(Value object) { + return object.asBytes(); + } + + @Override + public Value deserialize(byte[] stream) { + return new Value(stream); + } + };*/ + + + public final static SerializerIfc BlockHeaderSerializer = new SerializerIfc() { + @Override + public byte[] serialize(BlockHeader object) { + return object == null ? null : object.encode(); + } + + @Override + public BlockHeader deserialize(byte[] stream) { + return stream == null ? null : new BlockHeader(stream); + } + }; + + /** + * AS IS serializer (doesn't change anything) + */ + public final static SerializerIfc AsIsSerializer = new SerializerIfc() { + @Override + public byte[] serialize(byte[] object) { + return object; + } + + @Override + public byte[] deserialize(byte[] stream) { + return stream; + } + }; +} diff --git a/core/src/main/java/org/platon/core/datasource/SourceCodec.java b/core/src/main/java/org/platon/core/datasource/SourceCodec.java new file mode 100644 index 0000000..69c791b --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/SourceCodec.java @@ -0,0 +1,66 @@ +package org.platon.core.datasource; + +import org.platon.storage.datasource.AbstractChainedSource; +import org.platon.storage.datasource.SerializerIfc; +import org.platon.storage.datasource.Source; + +public class SourceCodec + extends AbstractChainedSource { + + protected SerializerIfc keySerializer; + protected SerializerIfc valSerializer; + + public SourceCodec(Source src, SerializerIfc keySerializer, SerializerIfc valSerializer) { + super(src); + this.keySerializer = keySerializer; + this.valSerializer = valSerializer; + setFlushSource(true); + } + + @Override + public void put(Key key, Value val) { + getSource().put(keySerializer.serialize(key), valSerializer.serialize(val)); + } + + @Override + public Value get(Key key) { + return valSerializer.deserialize(getSource().get(keySerializer.serialize(key))); + } + + @Override + public void delete(Key key) { + getSource().delete(keySerializer.serialize(key)); + } + + @Override + public boolean flushImpl() { + return false; + } + + /** + * Shortcut class when only value conversion is required + */ + public static class ValueOnly extends SourceCodec { + public ValueOnly(Source src, SerializerIfc valSerializer) { + super(src, new Serializers.Identity(), valSerializer); + } + } + + /** + * Shortcut class when only key conversion is required + */ + public static class KeyOnly extends SourceCodec { + public KeyOnly(Source src, SerializerIfc keySerializer) { + super(src, keySerializer, new Serializers.Identity()); + } + } + + /** + * Shortcut class when only value conversion is required and keys are of byte[] type + */ + public static class BytesKey extends ValueOnly { + public BytesKey(Source src, SerializerIfc valSerializer) { + super(src, valSerializer); + } + } +} diff --git a/core/src/main/java/org/platon/core/datasource/TransactionStore.java b/core/src/main/java/org/platon/core/datasource/TransactionStore.java new file mode 100644 index 0000000..18df845 --- /dev/null +++ b/core/src/main/java/org/platon/core/datasource/TransactionStore.java @@ -0,0 +1,109 @@ +package org.platon.core.datasource; + +import org.apache.commons.collections4.map.LRUMap; +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteComparator; +import org.platon.core.TransactionInfo; +import org.platon.core.codec.TransactionInfoCodec; +import org.platon.core.proto.TransactionInfoMessage; +import org.platon.core.proto.TransactionInfoMessageList; +import org.platon.storage.datasource.SerializerIfc; +import org.platon.storage.datasource.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; + + +public class TransactionStore extends ObjectDataSource> { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_DB); + + + private final LRUMap lastSavedTxHash = new LRUMap<>(5000); + + private final Object object = new Object(); + + private final static SerializerIfc, byte[]> serializer = + new SerializerIfc, byte[]>() { + + @Override + public byte[] serialize(List object) { + + + TransactionInfoMessageList.Builder messageList = TransactionInfoMessageList.newBuilder(); + for (int i = 0; i < object.size(); i++) { + TransactionInfoMessage txInfoMessage = TransactionInfoCodec.encode(object.get(i)); + messageList.addTxInfoList(txInfoMessage); + } + return messageList.build().toByteArray(); + } + + @Override + public List deserialize(byte[] stream) { + try { + if (stream == null) return null; + TransactionInfoMessageList infoMessageList = TransactionInfoMessageList.parseFrom(stream); + List list = infoMessageList.getTxInfoListList(); + List ret = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + ret.add(TransactionInfoCodec.decode(list.get(i))); + } + return ret; + } catch (Exception e) { + + logger.error("TransactionStore. deserialize fail.", e); + } + return null; + } + }; + + + public boolean put(TransactionInfo tx) { + byte[] txHash = tx.getReceipt().getTransaction().getHash(); + + List existingInfos = null; + synchronized (lastSavedTxHash) { + if (lastSavedTxHash.put(new ByteArrayWrapper(txHash), object) != null || !lastSavedTxHash.isFull()) { + existingInfos = get(txHash); + } + } + + + + if (existingInfos == null) { + existingInfos = new ArrayList<>(); + } else { + for (TransactionInfo info : existingInfos) { + if (ByteComparator.equals(info.getBlockHash(), tx.getBlockHash())) { + return false; + } + } + } + existingInfos.add(tx); + put(txHash, existingInfos); + + return true; + } + + public TransactionInfo get(byte[] txHash, byte[] blockHash) { + List existingInfos = get(txHash); + for (TransactionInfo info : existingInfos) { + if (ByteComparator.equals(info.getBlockHash(), blockHash)) { + return info; + } + } + return null; + } + + public TransactionStore(Source src) { + super(src, serializer, 500); + } + + @PreDestroy + public void close() { + } +} diff --git a/core/src/main/java/org/platon/core/db/AbstractBlockstore.java b/core/src/main/java/org/platon/core/db/AbstractBlockstore.java new file mode 100644 index 0000000..2dd7086 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/AbstractBlockstore.java @@ -0,0 +1,18 @@ +package org.platon.core.db; + +import org.platon.core.block.Block; + +public abstract class AbstractBlockstore implements BlockStoreIfc { + + @Override + public byte[] getBlockHashByNumber(long blockNumber, byte[] branchBlockHash) { + Block branchBlock = getBlockByHash(branchBlockHash); + if (branchBlock.getBlockHeader().getNumber() < blockNumber) { + throw new IllegalArgumentException("Requested block number > branch hash number: " + blockNumber + " < " + branchBlock.getBlockHeader().getNumber()); + } + while(branchBlock.getBlockHeader().getNumber() > blockNumber) { + branchBlock = getBlockByHash(branchBlock.getBlockHeader().getParentHash()); + } + return branchBlock.getBlockHeader().getHash(); + } +} diff --git a/core/src/main/java/org/platon/core/db/BlockStoreIfc.java b/core/src/main/java/org/platon/core/db/BlockStoreIfc.java new file mode 100644 index 0000000..2e36c34 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/BlockStoreIfc.java @@ -0,0 +1,50 @@ +package org.platon.core.db; + +import org.platon.core.block.BlockHeader; +import org.platon.core.block.Block; + +import java.math.BigInteger; +import java.util.List; + +/** + * @author - Jungle + * @date 2018/9/3 14:48 + * @version 0.0.1 + */ +public interface BlockStoreIfc { + + byte[] getBlockHashByNumber(long blockNumber); + + + byte[] getBlockHashByNumber(long blockNumber, byte[] branchBlockHash); + + Block getChainBlockByNumber(long blockNumber); + + Block getBlockByHash(byte[] hash); + + boolean isBlockExist(byte[] hash); + + List getListHashesEndWith(byte[] hash, long qty); + + List getListHeadersEndWith(byte[] hash, long qty); + + List getListBlocksEndWith(byte[] hash, long qty); + + void saveBlock(Block block, BigInteger totalDifficulty, boolean mainChain); + + BigInteger getTotalDifficultyForHash(byte[] hash); + + BigInteger getTotalDifficulty(); + + Block getBestBlock(); + + long getMaxNumber(); + + void flush(); + + void reBranch(Block forkBlock); + + void load(); + + void close(); +} diff --git a/core/src/main/java/org/platon/core/db/BlockStoreImpl.java b/core/src/main/java/org/platon/core/db/BlockStoreImpl.java new file mode 100644 index 0000000..8014786 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/BlockStoreImpl.java @@ -0,0 +1,467 @@ +package org.platon.core.db; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteComparator; +import org.platon.common.utils.Numeric; +import org.platon.core.block.BlockHeader; +import org.platon.core.proto.BlockInfo; +import org.platon.core.proto.BlockInfoList; +import org.platon.core.block.Block; +import org.platon.core.datasource.DataSourceArray; +import org.platon.core.datasource.ObjectDataSource; +import org.platon.storage.datasource.SerializerIfc; +import org.platon.storage.datasource.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static java.math.BigInteger.ZERO; +import static org.bouncycastle.util.Arrays.areEqual; + + +public class BlockStoreImpl extends AbstractBlockstore { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + + Source indexDS; + DataSourceArray> index; + + Source blocksDS; + ObjectDataSource blocks; + + public BlockStoreImpl() { + } + + public void init(Source index, Source blocks) { + indexDS = index; + this.index = new DataSourceArray<>(new ObjectDataSource<>(index, BLOCK_INFO_SERIALIZER,512)); + this.blocksDS = blocks; + this.blocks = new ObjectDataSource<>(blocks, new SerializerIfc() { + @Override + public byte[] serialize(Block block) { + return block.encode(); + } + + @Override + public Block deserialize(byte[] bytes) { + return bytes == null ? null : new Block(bytes); + } + },256); + } + + public synchronized Block getBestBlock() { + + Long maxLevel = getMaxNumber(); + if (maxLevel < 0) return null; + + Block bestBlock = getChainBlockByNumber(maxLevel); + if (bestBlock != null) return bestBlock; + + + + + + while (bestBlock == null) { + --maxLevel; + bestBlock = getChainBlockByNumber(maxLevel); + } + return bestBlock; + } + + public synchronized byte[] getBlockHashByNumber(long blockNumber) { + Block chainBlock = getChainBlockByNumber(blockNumber); + return chainBlock == null ? null : chainBlock.getBlockHeader().getHash(); + } + + @Override + public synchronized void flush() { + blocks.flush(); + index.flush(); + blocksDS.flush(); + indexDS.flush(); + } + + @Override + public synchronized void saveBlock(Block block, BigInteger totalDifficulty, boolean mainChain) { + addInternalBlock(block, totalDifficulty, mainChain); + } + + private void addInternalBlock(Block block, BigInteger totalDifficulty, boolean mainChain) { + + List blockInfos = block.getBlockHeader().getNumber() >= index.size() ? null : index.get((int) block.getBlockHeader().getNumber()); + blockInfos = blockInfos == null ? new ArrayList() : blockInfos; + + BlockInfo.Builder blockInfo = BlockInfo.newBuilder(); + blockInfo.setTotalDifficulty(ByteString.copyFrom(totalDifficulty.toByteArray())); + blockInfo.setHash(ByteString.copyFrom(block.getBlockHeader().getHash())); + + blockInfo.setMainChain(mainChain); + + putBlockInfo(blockInfos, blockInfo.build()); + index.set((int) block.getBlockHeader().getNumber(), blockInfos); + + blocks.put(block.getBlockHeader().getHash(), block); + } + + private void putBlockInfo(List blockInfos, BlockInfo blockInfo) { + for (int i = 0; i < blockInfos.size(); i++) { + BlockInfo curBlockInfo = blockInfos.get(i); + if (ByteComparator.equals(curBlockInfo.getHash().toByteArray(), blockInfo.getHash().toByteArray())) { + blockInfos.set(i, blockInfo); + return; + } + } + blockInfos.add(blockInfo); + } + + public synchronized List getBlocksByNumber(long number) { + + List result = new ArrayList<>(); + + if (number >= index.size()) { + return result; + } + + + List blockInfos = index.get((int) number); + + if (blockInfos == null) { + return result; + } + + for (BlockInfo blockInfo : blockInfos) { + + byte[] hash = blockInfo.getHash().toByteArray(); + Block block = blocks.get(hash); + + result.add(block); + } + return result; + } + + @Override + public synchronized Block getChainBlockByNumber(long number) { + if (number >= index.size()) { + return null; + } + + List blockInfos = index.get((int) number); + + if (blockInfos == null) { + return null; + } + + for (BlockInfo blockInfo : blockInfos) { + + if (blockInfo.getMainChain()) { + + byte[] hash = blockInfo.getHash().toByteArray(); + return blocks.get(hash); + } + } + + return null; + } + + @Override + public synchronized Block getBlockByHash(byte[] hash) { + return blocks.get(hash); + } + + @Override + public synchronized boolean isBlockExist(byte[] hash) { + return blocks.get(hash) != null; + } + + @Override + public synchronized BigInteger getTotalDifficultyForHash(byte[] hash) { + Block block = this.getBlockByHash(hash); + if (block == null) return ZERO; + + Long level = block.getBlockHeader().getNumber(); + List blockInfos = index.get(level.intValue()); + for (BlockInfo blockInfo : blockInfos) { + if (areEqual(blockInfo.getHash().toByteArray(), hash)) { + return Numeric.toBigInt(blockInfo.getTotalDifficulty().toByteArray()); + } + } + + return ZERO; + } + + @Override + public synchronized BigInteger getTotalDifficulty() { + long maxNumber = getMaxNumber(); + + List blockInfos = index.get((int) maxNumber); + for (BlockInfo blockInfo : blockInfos) { + if (blockInfo.getMainChain()) { + return Numeric.toBigInt(blockInfo.getTotalDifficulty().toByteArray()); + } + } + + while (true) { + --maxNumber; + List infos = getBlockInfoForLevel(maxNumber); + + for (BlockInfo blockInfo : infos) { + if (blockInfo.getMainChain()) { + return Numeric.toBigInt(blockInfo.getTotalDifficulty().toByteArray()); + } + } + } + } + + public synchronized void updateTotDifficulties(long index) { + List level = getBlockInfoForLevel(index); + for (BlockInfo blockInfo : level) { + + BlockInfo.Builder blockInfoBuilder = blockInfo.toBuilder(); + Block block = getBlockByHash(blockInfo.getHash().toByteArray()); + List parentInfos = getBlockInfoForLevel(index - 1); + BlockInfo parentInfo = getBlockInfoForHash(parentInfos, block.getBlockHeader().getParentHash()); + + BigInteger parentTotalDifficulty = Numeric.toBigInt(parentInfo.getTotalDifficulty().toByteArray()); + BigInteger blockTotalDifficulty = Numeric.toBigInt(parentInfo.getTotalDifficulty().toByteArray()); + BigInteger finalTotalDifficulty = parentTotalDifficulty.add(blockTotalDifficulty); + blockInfoBuilder.setTotalDifficulty(ByteString.copyFrom(finalTotalDifficulty.toByteArray())); + } + this.index.set((int) index, level); + } + + @Override + public synchronized long getMaxNumber() { + + Long bestIndex = 0L; + + if (index.size() > 0) { + bestIndex = (long) index.size(); + } + + return bestIndex - 1L; + } + + @Override + public synchronized List getListHashesEndWith(byte[] hash, long number) { + + List blocks = getListBlocksEndWith(hash, number); + List hashes = new ArrayList<>(blocks.size()); + + for (Block b : blocks) { + hashes.add(b.getBlockHeader().getHash()); + } + + return hashes; + } + + @Override + public synchronized List getListHeadersEndWith(byte[] hash, long qty) { + + List blocks = getListBlocksEndWith(hash, qty); + List headers = new ArrayList<>(blocks.size()); + + for (Block b : blocks) { + headers.add(b.getBlockHeader()); + } + + return headers; + } + + @Override + public synchronized List getListBlocksEndWith(byte[] hash, long qty) { + return getListBlocksEndWithInner(hash, qty); + } + + private List getListBlocksEndWithInner(byte[] hash, long qty) { + + Block block = this.blocks.get(hash); + + if (block == null) return new ArrayList<>(); + + List blocks = new ArrayList<>((int) qty); + + for (int i = 0; i < qty; ++i) { + blocks.add(block); + block = this.blocks.get(block.getBlockHeader().getParentHash()); + if (block == null) break; + } + + return blocks; + } + + @Override + public synchronized void reBranch(Block forkBlock) { + + Block bestBlock = getBestBlock(); + + long maxLevel = Math.max(bestBlock.getBlockHeader().getNumber(), forkBlock.getBlockHeader().getNumber()); + + + long currentLevel = maxLevel; + Block forkLine = forkBlock; + if (forkBlock.getBlockHeader().getNumber() > bestBlock.getBlockHeader().getNumber()) { + + while (currentLevel > bestBlock.getBlockHeader().getNumber()) { + List blocks = getBlockInfoForLevel(currentLevel); + BlockInfo blockInfo = getBlockInfoForHash(blocks, forkLine.getBlockHeader().getHash()); + + BlockInfo.Builder blockInfoBuilder = blockInfo.toBuilder(); + if (blockInfo != null) { + blockInfoBuilder.setMainChain(true); + setBlockInfoForLevel(currentLevel, blocks); + } + forkLine = getBlockByHash(forkLine.getBlockHeader().getParentHash()); + --currentLevel; + } + } + + Block bestLine = bestBlock; + if (bestBlock.getBlockHeader().getNumber() > forkBlock.getBlockHeader().getNumber()) { + + while (currentLevel > forkBlock.getBlockHeader().getNumber()) { + + List blocks = getBlockInfoForLevel(currentLevel); + BlockInfo blockInfo = getBlockInfoForHash(blocks, bestLine.getBlockHeader().getHash()); + BlockInfo.Builder blockInfoBuilder = blockInfo.toBuilder(); + if (blockInfo != null) { + blockInfoBuilder.setMainChain(false); + setBlockInfoForLevel(currentLevel, blocks); + } + bestLine = getBlockByHash(bestLine.getBlockHeader().getParentHash()); + --currentLevel; + } + } + + + while (!bestLine.isEqual(forkLine)) { + + List levelBlocks = getBlockInfoForLevel(currentLevel); + BlockInfo bestInfo = getBlockInfoForHash(levelBlocks, bestLine.getBlockHeader().getHash()); + BlockInfo.Builder bestInfoBuilder = bestInfo.toBuilder(); + if (bestInfo != null) { + bestInfoBuilder.setMainChain(false); + setBlockInfoForLevel(currentLevel, levelBlocks); + } + BlockInfo forkInfo = getBlockInfoForHash(levelBlocks, forkLine.getBlockHeader().getHash()); + BlockInfo.Builder forkInfoBuilder = forkInfo.toBuilder(); + if (forkInfo != null) { + forkInfoBuilder.setMainChain(true); + setBlockInfoForLevel(currentLevel, levelBlocks); + } + bestLine = getBlockByHash(bestLine.getBlockHeader().getParentHash()); + forkLine = getBlockByHash(forkLine.getBlockHeader().getParentHash()); + + --currentLevel; + } + } + + public synchronized List getListHashesStartWith(long number, long maxBlocks) { + + List result = new ArrayList<>(); + + int i; + for (i = 0; i < maxBlocks; ++i) { + List blockInfos = index.get((int) number); + if (blockInfos == null) break; + + for (BlockInfo blockInfo : blockInfos) + + if (blockInfo.getMainChain()) { + result.add(blockInfo.getHash().toByteArray()); + break; + } + + ++number; + } + maxBlocks -= i; + + return result; + } + + + public static final SerializerIfc, byte[]> BLOCK_INFO_SERIALIZER = new SerializerIfc, byte[]>() { + + @Override + public byte[] serialize(List value) { + + BlockInfoList.Builder blockInfoListBuilder = BlockInfoList.newBuilder(); + + for (BlockInfo blockInfo : value) { + + BigInteger blockTotalDifficulty = Numeric.toBigInt(blockInfo.getTotalDifficulty().toByteArray()); + + + + blockInfoListBuilder.addBlockInfoList(blockInfo); + } + return blockInfoListBuilder.build().toByteArray(); + } + + @Override + public List deserialize(byte[] bytes) { + if (bytes == null) return null; + try { + BlockInfoList pbBlockInfoList = BlockInfoList.parseFrom(bytes); + List blockInfoList = pbBlockInfoList.getBlockInfoListList(); + return blockInfoList; + } catch (InvalidProtocolBufferException e) { + + throw new RuntimeException(e); + } + } + }; + + public synchronized void printChain() { + + Long number = getMaxNumber(); + + for (int i = 0; i < number; ++i) { + List levelInfos = index.get(i); + + if (levelInfos != null) { + System.out.print(i); + for (BlockInfo blockInfo : levelInfos) { + if (blockInfo.getMainChain()) + System.out.print(" [" + shortHash(blockInfo.getHash().toByteArray()) + "] "); + else + System.out.print(" " + shortHash(blockInfo.getHash().toByteArray()) + " "); + } + System.out.println(); + } + } + } + + private synchronized List getBlockInfoForLevel(long level) { + return index.get((int) level); + } + + private synchronized void setBlockInfoForLevel(long level, List infos) { + index.set((int) level, infos); + } + + private static BlockInfo getBlockInfoForHash(List blocks, byte[] hash) { + + for (BlockInfo blockInfo : blocks) + if (areEqual(hash, blockInfo.getHash().toByteArray())) return blockInfo; + + return null; + } + + @Override + public synchronized void load() { + } + + @Override + public synchronized void close() { + } + + public static String shortHash(byte[] hash) { + return Numeric.toHexString(hash).substring(0, 6); + } +} diff --git a/core/src/main/java/org/platon/core/db/ContractDetails.java b/core/src/main/java/org/platon/core/db/ContractDetails.java new file mode 100644 index 0000000..1e5f852 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/ContractDetails.java @@ -0,0 +1,65 @@ +package org.platon.core.db; + +import org.platon.common.wrapper.DataWord; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author - Jungle + * @date 2018/9/3 14:37 + * @version 0.0.1 + */ +public interface ContractDetails { + + void put(DataWord key, DataWord value); + + DataWord get(DataWord key); + + byte[] getCode(); + + byte[] getCode(byte[] codeHash); + + void setCode(byte[] code); + + byte[] getStorageHash(); + + void decode(byte[] rlpCode); + + void setDirty(boolean dirty); + + void setDeleted(boolean deleted); + + boolean isDirty(); + + boolean isDeleted(); + + byte[] getEncoded(); + + int getStorageSize(); + + Set getStorageKeys(); + + Map getStorage(@Nullable Collection keys); + + Map getStorage(); + + void setStorage(List storageKeys, List storageValues); + + void setStorage(Map storage); + + byte[] getAddress(); + + void setAddress(byte[] address); + + ContractDetails clone(); + + String toString(); + + void syncStorage(); + + ContractDetails getSnapshotTo(byte[] hash); +} diff --git a/core/src/main/java/org/platon/core/db/DbFlushManager.java b/core/src/main/java/org/platon/core/db/DbFlushManager.java new file mode 100644 index 0000000..9a3704d --- /dev/null +++ b/core/src/main/java/org/platon/core/db/DbFlushManager.java @@ -0,0 +1,184 @@ +package org.platon.core.db; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.platon.common.AppenderName; +import org.platon.core.config.CoreConfig; +import org.platon.core.listener.CompoplexPlatonListener; +import org.platon.storage.datasource.AbstractCachedSource; +import org.platon.storage.datasource.AsyncFlushable; +import org.platon.storage.datasource.DbSource; +import org.platon.storage.datasource.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.*; + +public class DbFlushManager { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_DB); + + List> writeCaches = new CopyOnWriteArrayList<>(); + List> sources = new CopyOnWriteArrayList<>(); + Set dbSources = new HashSet<>(); + AbstractCachedSource stateDbCache; + + long sizeThreshold; + int commitsCountThreshold; + boolean syncDone = false; + boolean flushAfterSyncDone; + + CoreConfig config; + + int commitCount = 0; + + private final BlockingQueue executorQueue = new ArrayBlockingQueue<>(1); + + private final ExecutorService flushThread = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + executorQueue, new ThreadFactoryBuilder().setNameFormat("DbFlushManagerThread-%d").build()); + + Future lastFlush = Futures.immediateFuture(false); + + public DbFlushManager(CoreConfig config, Set dbSources, AbstractCachedSource stateDbCache) { + this.config = config; + this.dbSources = dbSources; + sizeThreshold = 64 * 1024 * 1024; + commitsCountThreshold = 0; + flushAfterSyncDone = true ; + this.stateDbCache = stateDbCache; + } + + @Autowired + public void setEthereumListener(CompoplexPlatonListener listener) { + if (!flushAfterSyncDone) return; + /*listener.addListener(new PlatonListenerAdapter() { + @Override + public void onSyncDone(SyncState state) { + if (state == SyncState.COMPLETE) { + logger.info("~> DbFlushManager: long sync done, flushing each block now"); + syncDone = true; + } + } + });*/ + } + + public void setSizeThreshold(long sizeThreshold) { + this.sizeThreshold = sizeThreshold; + } + + public void addCache(AbstractCachedSource cache) { + writeCaches.add(cache); + } + + public void addSource(Source src) { + sources.add(src); + } + + public long getCacheSize() { + long ret = 0; + for (AbstractCachedSource writeCache : writeCaches) { + ret += writeCache.estimateCacheSize(); + } + return ret; + } + + public synchronized void commit(Runnable atomicUpdate) { + atomicUpdate.run(); + commit(); + } + + public synchronized void commit() { + long cacheSize = getCacheSize(); + if (sizeThreshold >= 0 && cacheSize >= sizeThreshold) { + logger.info("DbFlushManager: flushing db due to write cache size (" + cacheSize + ") reached threshold (" + sizeThreshold + ")"); + flush(); + } else if (commitsCountThreshold > 0 && commitCount >= commitsCountThreshold) { + logger.info("DbFlushManager: flushing db due to commits (" + commitCount + ") reached threshold (" + commitsCountThreshold + ")"); + flush(); + commitCount = 0; + } else if (flushAfterSyncDone && syncDone) { + logger.debug("DbFlushManager: flushing db due to short sync"); + flush(); + } + commitCount++; + } + + public synchronized void flushSync() { + try { + flush().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public synchronized Future flush() { + if (!lastFlush.isDone()) { + logger.info("Waiting for previous flush to complete..."); + try { + lastFlush.get(); + } catch (Exception e) { + logger.error("Error during last flush", e); + } + } + logger.debug("Flipping async storages"); + for (AbstractCachedSource writeCache : writeCaches) { + try { + if (writeCache instanceof AsyncFlushable) { + ((AsyncFlushable) writeCache).flipStorage(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + logger.debug("Submitting flush task"); + return lastFlush = flushThread.submit(() -> { + boolean ret = false; + long s = System.nanoTime(); + logger.info("Flush started"); + + sources.forEach(Source::flush); + + for (AbstractCachedSource writeCache : writeCaches) { + if (writeCache instanceof AsyncFlushable) { + try { + ret |= ((AsyncFlushable) writeCache).flushAsync().get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } else { + ret |= writeCache.flush(); + } + } + if (stateDbCache != null) { + logger.debug("Flushing to DB"); + stateDbCache.flush(); + } + logger.info("Flush completed in " + (System.nanoTime() - s) / 1000000 + " ms"); + + return ret; + }); + } + + /** + * Flushes all caches and closes all databases + */ + public synchronized void close() { + logger.info("Flushing DBs..."); + flushSync(); + logger.info("Flush done."); + for (DbSource dbSource : dbSources) { + logger.info("Closing DB: {}", dbSource.getName()); + try { + dbSource.close(); + } catch (Exception ex) { + logger.error(String.format("Caught error while closing DB: %s", dbSource.getName()), ex); + } + } + } +} + diff --git a/core/src/main/java/org/platon/core/db/HeaderStore.java b/core/src/main/java/org/platon/core/db/HeaderStore.java new file mode 100644 index 0000000..b04178b --- /dev/null +++ b/core/src/main/java/org/platon/core/db/HeaderStore.java @@ -0,0 +1,82 @@ +package org.platon.core.db; + +import org.platon.common.AppenderName; +import org.platon.core.block.BlockHeader; +import org.platon.core.datasource.Serializers; +import org.platon.core.datasource.DataSourceArray; +import org.platon.core.datasource.ObjectDataSource; +import org.platon.storage.datasource.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class HeaderStore { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + Source indexDS; + DataSourceArray index; + Source headersDS; + ObjectDataSource headers; + + public HeaderStore() { + } + + public void init(Source index, Source headers) { + indexDS = index; + this.index = new DataSourceArray<>( + new ObjectDataSource<>(index,Serializers.AsIsSerializer, 2048)); + this.headersDS = headers; + this.headers = new ObjectDataSource<>(headers, Serializers.BlockHeaderSerializer, 512); + } + + + public synchronized BlockHeader getBestHeader() { + + long maxNumber = getMaxNumber(); + if (maxNumber < 0) return null; + + return getHeaderByNumber(maxNumber); + } + + public synchronized void flush() { + headers.flush(); + index.flush(); + headersDS.flush(); + indexDS.flush(); + } + + public synchronized void saveHeader(BlockHeader header) { + index.set((int) header.getNumber(), header.getHash()); + headers.put(header.getHash(), header); + } + + public synchronized BlockHeader getHeaderByNumber(long number) { + if (number < 0 || number >= index.size()) { + return null; + } + + byte[] hash = index.get((int) number); + if (hash == null) { + return null; + } + + return headers.get(hash); + } + + public synchronized int size() { + return index.size(); + } + + public synchronized BlockHeader getHeaderByHash(byte[] hash) { + return headers.get(hash); + } + + public synchronized long getMaxNumber(){ + if (index.size() > 0) { + return (long) index.size() - 1; + } else { + return -1; + } + } +} diff --git a/core/src/main/java/org/platon/core/db/RepositoryImpl.java b/core/src/main/java/org/platon/core/db/RepositoryImpl.java new file mode 100644 index 0000000..133659e --- /dev/null +++ b/core/src/main/java/org/platon/core/db/RepositoryImpl.java @@ -0,0 +1,416 @@ +package org.platon.core.db; + +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteComparator; +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.enums.AccountTypeEnum; +import org.platon.core.Repository; +import org.platon.core.block.Block; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.math.BigInteger; +import java.util.*; + +public class RepositoryImpl implements Repository { + + private final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + protected RepositoryImpl parent; + + protected Source accountCache; + protected Source codeCache; + protected MultiCache> storageCache; + + protected RepositoryImpl() { + } + + + protected void init(Source accountCache, Source codeCache, + MultiCache> storageCache) { + + this.accountCache = accountCache; + this.codeCache = codeCache; + this.storageCache = storageCache; + } + + public RepositoryImpl(Source AccountCache, Source codeCache, + MultiCache> storageCache) { + init(AccountCache, codeCache, storageCache); + } + + @Override + public synchronized boolean isContract(byte[] addr) { + Account account = getAccount(addr); + if(null == account) { + return false; + } + return account.getAccountType() == AccountTypeEnum.ACCOUNT_TYPE_CONTRACT; + } + + @Override + public synchronized Account createAccount(byte[] addr) { + Account state = new Account(BigInteger.ZERO, + org.platon.core.transaction.util.HashUtil.EMPTY_ROOT, + org.platon.core.transaction.util.HashUtil.EMPTY_ROOT, + org.platon.core.transaction.util.HashUtil.EMPTY_ROOT); + accountCache.put(addr, state); + return state; + } + + @Override + public synchronized boolean isExist(byte[] addr) { + return getAccount(addr) != null; + } + + @Override + public synchronized Account getAccount(byte[] addr) { + return accountCache.get(addr); + } + + synchronized Account getOrCreateAccount(byte[] addr) { + Account ret = accountCache.get(addr); + if (ret == null) { + ret = createAccount(addr); + } + return ret; + } + + @Override + public synchronized void delete(byte[] addr) { + accountCache.delete(addr); + storageCache.delete(addr); + } + + @Override + public synchronized ContractDetails getContractDetails(byte[] addr) { + return new ContractDetailsImpl(addr); + } + + @Override + public synchronized boolean hasContractDetails(byte[] addr) { + return getContractDetails(addr) != null; + } + + @Override + public synchronized void saveCode(byte[] addr, byte[] code) { + byte[] codeHash = HashUtil.sha3(code); + codeCache.put(codeKey(codeHash, addr), code); + Account account = getOrCreateAccount(addr); + account.setBin(code); + accountCache.put(addr, account); + } + + @Override + public synchronized byte[] getCode(byte[] addr) { + byte[] codeHash = getCodeHash(addr); + return ByteComparator.equals(codeHash, HashUtil.EMPTY_DATA_HASH) ? + ByteUtil.EMPTY_BYTE_ARRAY : codeCache.get(codeKey(codeHash, addr)); + } + + // composing a key as there can be several contracts with the same code + private byte[] codeKey(byte[] codeHash, byte[] addr) { + return NodeKeyCompositor.compose(codeHash, addr); + } + + @Override + public byte[] getCodeHash(byte[] addr) { + Account account = getAccount(addr); + return account != null ? account.getBinHash() : HashUtil.EMPTY_DATA_HASH; + } + + @Override + public synchronized void addStorageRow(byte[] addr, DataWord key, DataWord value) { + getOrCreateAccount(addr); + Source contractStorage = storageCache.get(addr); + contractStorage.put(key, value.isZero() ? null : value); + } + + @Override + public synchronized DataWord getStorageValue(byte[] addr, DataWord key) { + Account account = getAccount(addr); + return account == null ? null : storageCache.get(addr).get(key); + } + + @Override + public synchronized BigInteger getBalance(byte[] addr) { + Account account = getAccount(addr); + return account == null ? BigInteger.ZERO : account.getBalance(); + } + + @Override + public synchronized BigInteger addBalance(byte[] addr, BigInteger value) { + Account account = getOrCreateAccount(addr); + account.addBalance(value); + accountCache.put(addr, account); + return account.getBalance(); + } + + @Override + public synchronized BigInteger subBalance(byte[] addr, BigInteger value) { + if(null == value) { + value = BigInteger.ZERO; + } + return addBalance(addr, value.negate()); + } + + @Override + public synchronized RepositoryImpl startTracking() { + Source trackAccountCache = new WriteCache.BytesKey<>(accountCache, WriteCache.CacheType.SIMPLE); + Source trackCodeCache = new WriteCache.BytesKey<>(codeCache, WriteCache.CacheType.SIMPLE); + MultiCache> trackStorageCache = new MultiCache(storageCache) { + @Override + protected CachedSource create(byte[] key, CachedSource srcCache) { + return new WriteCache<>(srcCache, WriteCache.CacheType.SIMPLE); + } + }; + + RepositoryImpl ret = new RepositoryImpl(trackAccountCache, trackCodeCache, trackStorageCache); + ret.parent = this; + return ret; + } + + @Override + public synchronized Repository getSnapshotTo(byte[] root) { + return parent.getSnapshotTo(root); + } + + @Override + public int getStorageSize(byte[] addr) { + throw new RuntimeException("Not supported"); + } + + @Override + public Set getStorageKeys(byte[] addr) { + throw new RuntimeException("Not supported"); + } + + @Override + public Map getStorage(byte[] addr, @Nullable Collection keys) { + throw new RuntimeException("Not supported"); + } + + @Override + public synchronized void commit() { + Repository parentSync = parent == null ? this : parent; + // need to synchronize on parent since between different caches flush + // the parent repo would not be in consistent state + // when no parent just take this instance as a mock + synchronized (parentSync) { + storageCache.flush(); + codeCache.flush(); + accountCache.flush(); + } + } + + @Override + public synchronized void rollback() { + // nothing to do, will be GCed + } + + @Override + public byte[] getRoot() { + throw new RuntimeException("Not supported"); + } + + public synchronized String getTrieDump() { + return dumpStateTrie(); + } + + public String dumpStateTrie() { + throw new RuntimeException("Not supported"); + } + + class ContractDetailsImpl implements ContractDetails { + + private byte[] address; + + public ContractDetailsImpl(byte[] address) { + this.address = address; + } + + @Override + public void put(DataWord key, DataWord value) { + RepositoryImpl.this.addStorageRow(address, key, value); + } + + @Override + public DataWord get(DataWord key) { + return RepositoryImpl.this.getStorageValue(address, key); + } + + @Override + public byte[] getCode() { + return RepositoryImpl.this.getCode(address); + } + + @Override + public byte[] getCode(byte[] codeHash) { + throw new RuntimeException("Not supported"); + } + + @Override + public void setCode(byte[] code) { + RepositoryImpl.this.saveCode(address, code); + } + + @Override + public byte[] getStorageHash() { + throw new RuntimeException("Not supported"); + } + + @Override + public void decode(byte[] rlpCode) { + throw new RuntimeException("Not supported"); + } + + @Override + public void setDirty(boolean dirty) { + throw new RuntimeException("Not supported"); + } + + @Override + public void setDeleted(boolean deleted) { + RepositoryImpl.this.delete(address); + } + + @Override + public boolean isDirty() { + throw new RuntimeException("Not supported"); + } + + @Override + public boolean isDeleted() { + throw new RuntimeException("Not supported"); + } + + @Override + public byte[] getEncoded() { + throw new RuntimeException("Not supported"); + } + + @Override + public int getStorageSize() { + throw new RuntimeException("Not supported"); + } + + @Override + public Set getStorageKeys() { + throw new RuntimeException("Not supported"); + } + + @Override + public Map getStorage(@Nullable Collection keys) { + throw new RuntimeException("Not supported"); + } + + @Override + public Map getStorage() { + throw new RuntimeException("Not supported"); + } + + @Override + public void setStorage(List storageKeys, List storageValues) { + throw new RuntimeException("Not supported"); + } + + @Override + public void setStorage(Map storage) { + throw new RuntimeException("Not supported"); + } + + @Override + public byte[] getAddress() { + return address; + } + + @Override + public void setAddress(byte[] address) { + throw new RuntimeException("Not supported"); + } + + @Override + public ContractDetails clone() { + throw new RuntimeException("Not supported"); + } + + @Override + public void syncStorage() { + throw new RuntimeException("Not supported"); + } + + @Override + public ContractDetails getSnapshotTo(byte[] hash) { + throw new RuntimeException("Not supported"); + } + } + + + @Override + public Set getAccountsKeys() { + throw new RuntimeException("Not supported"); + } + + @Override + public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) { + throw new RuntimeException("Not supported"); + } + + @Override + public void flush() { + throw new RuntimeException("Not supported"); + } + + + @Override + public void flushNoReconnect() { + throw new RuntimeException("Not supported"); + } + + @Override + public void syncToRoot(byte[] root) { + throw new RuntimeException("Not supported"); + } + + @Override + public boolean isClosed() { + throw new RuntimeException("Not supported"); + } + + @Override + public void close() { + } + + @Override + public void reset() { + throw new RuntimeException("Not supported"); + } + + @Override + public void updateBatch(HashMap Accounts, HashMap contractDetailes) { + for (Map.Entry entry : Accounts.entrySet()) { + accountCache.put(entry.getKey().getData(), entry.getValue()); + } + for (Map.Entry entry : contractDetailes.entrySet()) { + ContractDetails details = getContractDetails(entry.getKey().getData()); + for (DataWord key : entry.getValue().getStorageKeys()) { + details.put(key, entry.getValue().get(key)); + } + byte[] code = entry.getValue().getCode(); + if (code != null && code.length > 0) { + details.setCode(code); + } + } + } + + @Override + public void loadAccount(byte[] addr, HashMap cacheAccounts, HashMap cacheDetails) { + throw new RuntimeException("Not supported"); + } +} diff --git a/core/src/main/java/org/platon/core/db/RepositoryRoot.java b/core/src/main/java/org/platon/core/db/RepositoryRoot.java new file mode 100644 index 0000000..69bc1b2 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/RepositoryRoot.java @@ -0,0 +1,134 @@ +package org.platon.core.db; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.Repository; +import org.platon.core.datasource.Serializers; +import org.platon.core.datasource.SourceCodec; +import org.platon.storage.trie.SecureTrie; +import org.platon.storage.trie.Trie; +import org.platon.storage.trie.TrieImpl; +import org.platon.storage.datasource.*; + +public class RepositoryRoot extends RepositoryImpl { + + private static class StorageCache extends ReadWriteCache { + + Trie trie; + + public StorageCache(Trie trie) { + super(new SourceCodec<>(trie, Serializers.StorageKeySerializer, + Serializers.StorageValueSerializer), WriteCache.CacheType.SIMPLE); + this.trie = trie; + } + } + + private class MultiStorageCache extends MultiCache { + + public MultiStorageCache() { + super(null); + } + + @Override + protected synchronized StorageCache create(byte[] key, StorageCache srcCache) { + Account account = accountCache.get(key); + SerializerIfc keyCompositor = new NodeKeyCompositor(key); + Source composingSrc = new SourceCodec.KeyOnly<>(trieCache, keyCompositor); + TrieImpl storageTrie = createTrie(composingSrc, account == null ? null : account.getStorageRoot()); + return new StorageCache(storageTrie); + } + + @Override + protected synchronized boolean flushChild(byte[] key, StorageCache childCache) { + if (super.flushChild(key, childCache)) { + if (childCache != null) { + Account storageOwnerAccount = accountCache.get(key); + // need to update account storage root + childCache.trie.flush(); + byte[] rootHash = childCache.trie.getRootHash(); + storageOwnerAccount.setStorageRoot(rootHash); + accountCache.put(key, storageOwnerAccount); + return true; + } else { + // account was deleted + return true; + } + } else { + // no storage changes + return false; + } + } + } + + private Source stateDS; + private CachedSource.BytesKey trieCache; + private Trie stateTrie; + + public RepositoryRoot(Source stateDS) { + this(stateDS, null); + } + + /** + * Building the following structure for snapshot Repository: + * + * stateDS --> trieCache --> stateTrie --> accountStateCodec --> accountStateCache + * \ \ + * \ \-->>> storageKeyCompositor --> contractStorageTrie --> storageCodec --> storageCache + * \--> codeCache + */ + public RepositoryRoot(final Source stateDS, byte[] root) { + this.stateDS = stateDS; + + trieCache = new WriteCache.BytesKey<>(stateDS, WriteCache.CacheType.COUNTING); + stateTrie = new SecureTrie(trieCache, root); + + SourceCodec.BytesKey accountCodec = new SourceCodec.BytesKey<>(stateTrie, Serializers.AccountStateSerializer); + final ReadWriteCache.BytesKey accountCache = new ReadWriteCache.BytesKey<>(accountCodec, WriteCache.CacheType.SIMPLE); + + final MultiCache storageCache = new MultiStorageCache(); + + // counting as there can be 2 contracts with the same code, 1 can suicide + Source codeCache = new WriteCache.BytesKey<>(stateDS, WriteCache.CacheType.COUNTING); + init(accountCache, codeCache, storageCache); + } + + @Override + public synchronized void commit() { + super.commit(); + stateTrie.flush(); + trieCache.flush(); + } + + @Override + public synchronized byte[] getRoot() { + storageCache.flush(); + accountCache.flush(); + + return stateTrie.getRootHash(); + } + + @Override + public synchronized void flush() { + commit(); + } + + @Override + public Repository getSnapshotTo(byte[] root) { + return new RepositoryRoot(stateDS, root); + } + + @Override + public synchronized void syncToRoot(byte[] root) { + stateTrie.setRoot(root); + } + + protected TrieImpl createTrie(Source trieCache, byte[] root) { + return new SecureTrie(trieCache, root); + } + + @Override + public synchronized String dumpStateTrie() { + return ((TrieImpl) stateTrie).dumpTrie(); + } + +} diff --git a/core/src/main/java/org/platon/core/db/RepositoryWrapper.java b/core/src/main/java/org/platon/core/db/RepositoryWrapper.java new file mode 100644 index 0000000..6458986 --- /dev/null +++ b/core/src/main/java/org/platon/core/db/RepositoryWrapper.java @@ -0,0 +1,193 @@ +package org.platon.core.db; + +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.block.Block; +import org.platon.core.BlockchainImpl; +import org.platon.core.Repository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@Component +public class RepositoryWrapper implements Repository { + + @Autowired + BlockchainImpl blockchain; + + public RepositoryWrapper() { + } + + @Override + public boolean isContract(byte[] addr) { + return blockchain.getRepository().isContract(addr); + } + + @Override + public Account createAccount(byte[] addr) { + return blockchain.getRepository().createAccount(addr); + } + + @Override + public boolean isExist(byte[] addr) { + return blockchain.getRepository().isExist(addr); + } + + @Override + public Account getAccount(byte[] addr) { + return blockchain.getRepository().getAccount(addr); + } + + @Override + public void delete(byte[] addr) { + blockchain.getRepository().delete(addr); + } + + @Override + public ContractDetails getContractDetails(byte[] addr) { + return blockchain.getRepository().getContractDetails(addr); + } + + @Override + public boolean hasContractDetails(byte[] addr) { + return blockchain.getRepository().hasContractDetails(addr); + } + + @Override + public void saveCode(byte[] addr, byte[] code) { + blockchain.getRepository().saveCode(addr, code); + } + + @Override + public byte[] getCode(byte[] addr) { + return blockchain.getRepository().getCode(addr); + } + + @Override + public byte[] getCodeHash(byte[] addr) { + return blockchain.getRepository().getCodeHash(addr); + } + + @Override + public void addStorageRow(byte[] addr, DataWord key, DataWord value) { + blockchain.getRepository().addStorageRow(addr, key, value); + } + + @Override + public DataWord getStorageValue(byte[] addr, DataWord key) { + return blockchain.getRepository().getStorageValue(addr, key); + } + + @Override + public BigInteger getBalance(byte[] addr) { + return blockchain.getRepository().getBalance(addr); + } + + @Override + public BigInteger addBalance(byte[] addr, BigInteger value) { + return blockchain.getRepository().addBalance(addr, value); + } + + @Override + public BigInteger subBalance(byte[] addr, BigInteger value) { + return blockchain.getRepository().subBalance(addr, value); + } + + @Override + public Set getAccountsKeys() { + return blockchain.getRepository().getAccountsKeys(); + } + + @Override + public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) { + blockchain.getRepository().dumpState(block, gasUsed, txNumber, txHash); + } + + @Override + public Repository startTracking() { + return blockchain.getRepository().startTracking(); + } + + @Override + public void flush() { + blockchain.getRepository().flush(); + } + + @Override + public void flushNoReconnect() { + blockchain.getRepository().flushNoReconnect(); + } + + @Override + public void commit() { + blockchain.getRepository().commit(); + } + + @Override + public void rollback() { + blockchain.getRepository().rollback(); + } + + @Override + public void syncToRoot(byte[] root) { + blockchain.getRepository().syncToRoot(root); + } + + @Override + public boolean isClosed() { + return blockchain.getRepository().isClosed(); + } + + @Override + public void close() { + blockchain.getRepository().close(); + } + + @Override + public void reset() { + blockchain.getRepository().reset(); + } + + @Override + public void updateBatch(HashMap accountStates, HashMap contractDetailes) { + blockchain.getRepository().updateBatch(accountStates, contractDetailes); + } + + @Override + public byte[] getRoot() { + return blockchain.getRepository().getRoot(); + } + + @Override + public void loadAccount(byte[] addr, HashMap cacheAccounts, HashMap cacheDetails) { + blockchain.getRepository().loadAccount(addr, cacheAccounts, cacheDetails); + } + + @Override + public Repository getSnapshotTo(byte[] root) { + return blockchain.getRepository().getSnapshotTo(root); + } + + @Override + public int getStorageSize(byte[] addr) { + return blockchain.getRepository().getStorageSize(addr); + } + + @Override + public Set getStorageKeys(byte[] addr) { + return blockchain.getRepository().getStorageKeys(addr); + } + + @Override + public Map getStorage(byte[] addr, @Nullable Collection keys) { + return blockchain.getRepository().getStorage(addr, keys); + } + +} diff --git a/core/src/main/java/org/platon/core/db/StateSource.java b/core/src/main/java/org/platon/core/db/StateSource.java new file mode 100644 index 0000000..5be9d3d --- /dev/null +++ b/core/src/main/java/org/platon/core/db/StateSource.java @@ -0,0 +1,72 @@ +package org.platon.core.db; + +import org.platon.core.config.CommonConfig; +import org.platon.core.datasource.JournalSource; +import org.platon.storage.datasource.*; +import org.springframework.beans.factory.annotation.Autowired; + +public class StateSource extends SourceChainBox + implements HashedKeySource { + + // for debug purposes + public static StateSource INST; + + JournalSource journalSource; + NoDeleteSource noDeleteSource; + + ReadCache readCache; + AbstractCachedSource writeCache; + + public StateSource(Source src, boolean pruningEnabled) { + super(src); + INST = this; + add(readCache = new ReadCache.BytesKey<>(src).withMaxCapacity(16 * 1024 * 1024 / 512)); // 512 - approx size of a node + readCache.setFlushSource(true); + writeCache = new AsyncWriteCache(readCache) { + @Override + protected WriteCache createCache(Source source) { + WriteCache.BytesKey ret = new WriteCache.BytesKey(source, WriteCache.CacheType.SIMPLE); + ret.withSizeEstimators(MemSizeEstimator.ByteArrayEstimator, MemSizeEstimator.ByteArrayEstimator); + ret.setFlushSource(true); + return ret; + } + }.withName("state"); + + add(writeCache); + + if (pruningEnabled) { + add(journalSource = new JournalSource<>(writeCache)); + } else { + add(noDeleteSource = new NoDeleteSource<>(writeCache)); + } + } + + @Autowired + public void setConfig() { + int size = 384; + readCache.withMaxCapacity(size * 1024 * 1024 / 512); // 512 - approx size of a node + } + + @Autowired + public void setCommonConfig(CommonConfig commonConfig) { + if (journalSource != null) { + journalSource.setJournalStore(commonConfig.cachedDbSource("journal")); + } + } + + public JournalSource getJournalSource() { + return journalSource; + } + + public Source getNoJournalSource() { + return writeCache; + } + + public AbstractCachedSource getWriteCache() { + return writeCache; + } + + public ReadCache getReadCache() { + return readCache; + } +} diff --git a/core/src/main/java/org/platon/core/enums/AccountTypeEnum.java b/core/src/main/java/org/platon/core/enums/AccountTypeEnum.java new file mode 100644 index 0000000..1c59af1 --- /dev/null +++ b/core/src/main/java/org/platon/core/enums/AccountTypeEnum.java @@ -0,0 +1,47 @@ +package org.platon.core.enums; + +/** + * AccountTypeEnum + * + * @author yanze + * @desc account type + * @create 2018-07-26 17:36 + **/ +public enum AccountTypeEnum { + ACCOUNT_TYPE_EXTERNAL(1,"external account"), + ACCOUNT_TYPE_CONTRACT(2,"contract account"); + + private String name; + private int code; + + private AccountTypeEnum(int code, String name){ + this.name = name; + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public static String getNameByCodeValue(int code){ + AccountTypeEnum[] allEnums = values(); + for(AccountTypeEnum enableStatus : allEnums){ + if(enableStatus.getCode()==code){ + return enableStatus.getName(); + } + } + return null; + } +} diff --git a/core/src/main/java/org/platon/core/enums/ExtraInfo.java b/core/src/main/java/org/platon/core/enums/ExtraInfo.java new file mode 100644 index 0000000..e56814f --- /dev/null +++ b/core/src/main/java/org/platon/core/enums/ExtraInfo.java @@ -0,0 +1,20 @@ +package org.platon.core.enums; + +/** + * Created by alliswell on 2018/8/8. + */ +public enum ExtraInfo { + BLOCKINFO((byte)1), + LOGBLOOM((byte)2), + RCECIPTS((byte)3), + TXPOS((byte)4), + BLOCKHASH((byte)5); + private byte value; + + ExtraInfo(byte value) { + this.value = value; + } + public byte getValue() { + return value; + } +} diff --git a/core/src/main/java/org/platon/core/exception/AlreadyKnownException.java b/core/src/main/java/org/platon/core/exception/AlreadyKnownException.java new file mode 100644 index 0000000..87fbe6a --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/AlreadyKnownException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/7/31. + */ +public class AlreadyKnownException extends RuntimeException { + public AlreadyKnownException(String message){ + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/exception/AlreadySealedException.java b/core/src/main/java/org/platon/core/exception/AlreadySealedException.java new file mode 100644 index 0000000..d4625a2 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/AlreadySealedException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/8/2. + */ +public class AlreadySealedException extends RuntimeException { + public AlreadySealedException(String message){ + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/exception/BadIrreversibleBlockException.java b/core/src/main/java/org/platon/core/exception/BadIrreversibleBlockException.java new file mode 100644 index 0000000..f6231b2 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/BadIrreversibleBlockException.java @@ -0,0 +1,13 @@ +package org.platon.core.exception; + +/** + * bad irreversible block exception + * + * @author alliswell + * @since 2018/08/13 + */ +public class BadIrreversibleBlockException extends RuntimeException { + public BadIrreversibleBlockException(String message){ + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/exception/ErrorGenesisBlockException.java b/core/src/main/java/org/platon/core/exception/ErrorGenesisBlockException.java new file mode 100644 index 0000000..015ee0d --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/ErrorGenesisBlockException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/8/10. + */ +public class ErrorGenesisBlockException extends RuntimeException { + public ErrorGenesisBlockException(String message){ + super(message); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/exception/OperationException.java b/core/src/main/java/org/platon/core/exception/OperationException.java new file mode 100644 index 0000000..12cadf9 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/OperationException.java @@ -0,0 +1,20 @@ +package org.platon.core.exception; + +public class OperationException extends Exception { + + private int errorCode; + + public OperationException(String message, int errorCode) { + + super(message); + this.errorCode = errorCode; + } + + public OperationException(String message) { + super(message); + } + + public OperationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/org/platon/core/exception/OutOfEnergonException.java b/core/src/main/java/org/platon/core/exception/OutOfEnergonException.java new file mode 100644 index 0000000..0c8dbd3 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/OutOfEnergonException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/7/26. + */ +public class OutOfEnergonException extends RuntimeException { + public OutOfEnergonException(String message){ + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/exception/OutOfPermissionException.java b/core/src/main/java/org/platon/core/exception/OutOfPermissionException.java new file mode 100644 index 0000000..39fe892 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/OutOfPermissionException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/8/2. + */ +public class OutOfPermissionException extends RuntimeException { + public OutOfPermissionException(String message) { + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/exception/PlatonException.java b/core/src/main/java/org/platon/core/exception/PlatonException.java new file mode 100644 index 0000000..39f8585 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/PlatonException.java @@ -0,0 +1,28 @@ +package org.platon.core.exception; + + +public class PlatonException extends RuntimeException { + + private int errorCode; + + public PlatonException(String message, int errorCode) { + super(message); + this.errorCode = errorCode; + } + + public PlatonException(String message) { + super(message); + } + + public PlatonException(String message, Throwable cause) { + super(message, cause); + } + + public int getErrorCode() { + return errorCode; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } +} diff --git a/core/src/main/java/org/platon/core/exception/UnknownParentException.java b/core/src/main/java/org/platon/core/exception/UnknownParentException.java new file mode 100644 index 0000000..c42cb37 --- /dev/null +++ b/core/src/main/java/org/platon/core/exception/UnknownParentException.java @@ -0,0 +1,10 @@ +package org.platon.core.exception; + +/** + * Created by alliswell on 2018/7/31. + */ +public class UnknownParentException extends RuntimeException { + public UnknownParentException(String message){ + super(message); + } +} diff --git a/core/src/main/java/org/platon/core/facade/CmdInterface.java b/core/src/main/java/org/platon/core/facade/CmdInterface.java new file mode 100644 index 0000000..6165964 --- /dev/null +++ b/core/src/main/java/org/platon/core/facade/CmdInterface.java @@ -0,0 +1,89 @@ +package org.platon.core.facade; + +import org.apache.commons.lang3.BooleanUtils; +import org.platon.common.AppenderName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class CmdInterface { + + private CmdInterface() { + } + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + public static void call(String[] args) { + try { + Map cliOptions = new HashMap<>(); + for (int i = 0; i < args.length; ++i) { + String arg = args[i]; + processHelp(arg); + + if (i + 1 >= args.length) + continue; + + if (processDbDirectory(arg, args[i + 1], cliOptions)) + continue; + if (processDbReset(arg, args[i + 1], cliOptions)) + continue; + } + + if (cliOptions.size() > 0) { + logger.info("Overriding config file with CLI options: {}", cliOptions); + } + + //ConfigProperties.getDefault().overrideParams(cliOptions); + + } catch (Throwable e) { + logger.error("Error parsing command line: [{}]", e.getMessage()); + System.exit(1); + } + } + + private static void processHelp(String arg) { + if ("--help".equals(arg)) { + printHelp(); + System.exit(1); + } + } + + private static boolean processDbDirectory(String arg, String db, Map cliOptions) { + if (!"-db".equals(arg)) + return false; + logger.info("DB directory set to [{}]", db); + //cliOptions.put(ConfigProperties.PROPERTY_DB_DIR, db); + return true; + } + + private static boolean processDbReset(String arg, String reset, Map cliOptions) { + if (!"-reset".equals(arg)) + return false; + Boolean resetFlag = interpret(reset); + if (resetFlag == null) { + throw new Error(String.format("Can't interpret DB reset arguments: %s %s", arg, reset)); + } + logger.info("Resetting db set to [{}]", resetFlag); + //cliOptions.put(ConfigProperties.PROPERTY_DB_RESET, resetFlag.toString()); + + return true; + } + + private static Boolean interpret(String arg) { + return BooleanUtils.toBooleanObject(arg); + } + + private static void printHelp() { + System.out.println("--help -- this help message "); + System.out.println("-reset -- reset yes/no the all database "); + System.out.println("-db -- to setup the path for the database directory "); + System.out.println(); + System.out.println("e.g: cli -reset no -db db-1 "); + System.out.println(); + } + +} diff --git a/core/src/main/java/org/platon/core/facade/Platon.java b/core/src/main/java/org/platon/core/facade/Platon.java new file mode 100644 index 0000000..316ca79 --- /dev/null +++ b/core/src/main/java/org/platon/core/facade/Platon.java @@ -0,0 +1,26 @@ +package org.platon.core.facade; + +import org.platon.core.TransactionInfo; +import org.platon.core.listener.PlatonListener; +import org.platon.core.Blockchain; +import org.platon.core.transaction.Transaction; + +import java.util.concurrent.Future; + +/** + * @author - Jungle + * @date 2018/9/13 14:14 + * @version 0.0.1 + */ +public interface Platon { + + Future submitTransaction(Transaction transaction); + + TransactionInfo getTransactionInfo(byte[] hash); + + Blockchain getBlockchain(); + + void addListener(PlatonListener listener); + + void close(); +} diff --git a/core/src/main/java/org/platon/core/facade/PlatonFactory.java b/core/src/main/java/org/platon/core/facade/PlatonFactory.java new file mode 100644 index 0000000..fe9cd21 --- /dev/null +++ b/core/src/main/java/org/platon/core/facade/PlatonFactory.java @@ -0,0 +1,38 @@ +package org.platon.core.facade; + +import org.platon.common.AppenderName; +import org.platon.common.config.ConfigProperties; +import org.platon.core.config.DefaultConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class PlatonFactory { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + public static Platon createPlaton() { + return createPlaton((Class) null); + } + + public static Platon createPlaton(Class userSpringConfig) { + return userSpringConfig == null ? + createPlaton(new Class[] {DefaultConfig.class}) : + createPlaton(DefaultConfig.class, userSpringConfig); + } + + public static Platon createPlaton(ConfigProperties config, Class userSpringConfig) { + return userSpringConfig == null ? + createPlaton(new Class[] {DefaultConfig.class}) : + createPlaton(DefaultConfig.class, userSpringConfig); + } + + public static Platon createPlaton(Class ... springConfigs) { + logger.info("~> Starting Platonj..."); + ApplicationContext context = new AnnotationConfigApplicationContext(springConfigs); + return context.getBean(Platon.class); + } +} diff --git a/core/src/main/java/org/platon/core/facade/PlatonImpl.java b/core/src/main/java/org/platon/core/facade/PlatonImpl.java new file mode 100644 index 0000000..8df0715 --- /dev/null +++ b/core/src/main/java/org/platon/core/facade/PlatonImpl.java @@ -0,0 +1,112 @@ +package org.platon.core.facade; + +import org.platon.common.AppenderName; +import org.platon.core.TransactionInfo; +import org.platon.core.listener.PlatonListener; +import org.platon.core.Blockchain; +import org.platon.core.ImportResult; +import org.platon.core.PendingStateIfc; +import org.platon.core.block.Block; +import org.platon.core.manager.InitialManager; +import org.platon.core.transaction.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.SmartLifecycle; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +@Component +public class PlatonImpl implements Platon, SmartLifecycle { + + private Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + @Autowired + private InitialManager initialManager; + + @Autowired + PendingStateIfc pendingState; + + @Autowired + ApplicationContext context; + + + public ImportResult addNewMinedBlock(Block block) { + ImportResult importResult = initialManager.getBlockchain().tryToConnect(block); + if (logger.isDebugEnabled()) { + logger.debug("~> PlatonImpl.addNewMinedBlock(), importBlock complete."); + } + if (importResult == ImportResult.IMPORTED_BEST) { + + } + return importResult; + } + + @Override + public Future submitTransaction(Transaction transaction) { + + pendingState.addPendingTransaction(transaction); + return CompletableFuture.completedFuture(transaction); + } + + @Override + public TransactionInfo getTransactionInfo(byte[] hash) { + return initialManager.getBlockchain().getTransactionInfo(hash); + } + + @Override + public Blockchain getBlockchain() { + return initialManager.getBlockchain(); + } + + @Override + public void addListener(PlatonListener listener) { + initialManager.addListener(listener); + } + + @Override + public void close() { + logger.info("Close application context."); + ((AbstractApplicationContext) getApplicationContext()).close(); + } + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { + logger.info("Shutting down Platon instance..."); + callback.run(); + } + + @Override + public void start() { + logger.info("Call start of PlatonImpl instance."); + } + + @Override + public void stop() { + logger.info("Call stop of PlatonImpl instance."); + } + + @Override + public boolean isRunning() { + return false; + } + + @Override + public int getPhase() { + return Integer.MIN_VALUE; + } + + public ApplicationContext getApplicationContext() { + return context; + } + +} diff --git a/core/src/main/java/org/platon/core/facade/PlatonServer.java b/core/src/main/java/org/platon/core/facade/PlatonServer.java new file mode 100644 index 0000000..b49361d --- /dev/null +++ b/core/src/main/java/org/platon/core/facade/PlatonServer.java @@ -0,0 +1,45 @@ +package org.platon.core.facade; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import org.platon.core.config.CoreConfig; +import org.platon.core.rpc.servant.AtpGrpcServiceImpl; + +import java.io.IOException; +import java.net.URISyntaxException; + +public class PlatonServer { + + private static Server server; + + public static void main(String args[]) throws IOException, URISyntaxException { + + + + final CoreConfig config = CoreConfig.getInstance(); + + PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(); + + + server = ServerBuilder + .forPort(11001) + .addService(new AtpGrpcServiceImpl()) + + .build().start(); + + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + System.out.println("shutdown..."); + server.shutdown(); + } + })); + + try { + server.awaitTermination(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/main/java/org/platon/core/genesis/GenesisConfig.java b/core/src/main/java/org/platon/core/genesis/GenesisConfig.java new file mode 100644 index 0000000..2185855 --- /dev/null +++ b/core/src/main/java/org/platon/core/genesis/GenesisConfig.java @@ -0,0 +1,79 @@ +package org.platon.core.genesis; + +import org.platon.common.utils.Numeric; +import org.platon.common.wrapper.DataWord; +import org.platon.core.config.BlockchainConfig; +import org.platon.core.config.Constants; +import org.platon.core.vm.EnergonCost; +import org.platon.core.vm.OpCode; +import org.platon.core.vm.program.Program; + +import java.math.BigInteger; + +/** + * GenesisConfig + * + * @author yanze + * @desc block chian config by json + * @create 2018-08-01 11:21 + **/ +public class GenesisConfig implements BlockchainConfig { + + private String consensus; + + private String energonLimit; + + public GenesisConfig() { + } + + public GenesisConfig(String consensus, String energonLimit) { + this.consensus = consensus; + this.energonLimit = energonLimit; + } + + @Override + public Constants getConstants() { + return null; + } + + @Override + public String getConsensus() { + return consensus; + } + + @Override + public BigInteger getEnergonLimit() { + return Numeric.toBigInt(energonLimit); + } + + @Override + public int getBlockProducingInterval() { + return 1000; + } + + //TODO Energon + @Override + public EnergonCost getEnergonCost() { + return null; + } + + //TODO Energon + @Override + public DataWord getCallEnergon(OpCode op, DataWord requestedEnergon, DataWord availableEnergon) throws Program.OutOfEnergonException { + return null; + } + + //TODO Energon + @Override + public DataWord getCreateEnergon(DataWord availableEnergon) { + return null; + } + + public void setConsensus(String consensus) { + this.consensus = consensus; + } + + public void setEnergonLimit(String energonLimit) { + this.energonLimit = energonLimit; + } +} diff --git a/core/src/main/java/org/platon/core/genesis/GenesisJson.java b/core/src/main/java/org/platon/core/genesis/GenesisJson.java new file mode 100644 index 0000000..77f381b --- /dev/null +++ b/core/src/main/java/org/platon/core/genesis/GenesisJson.java @@ -0,0 +1,117 @@ +package org.platon.core.genesis; + +import java.util.Map; + +/** + * GenesisJson + * + * @author yanze + * @desc genesis json model + * @create 2018-08-01 11:25 + **/ +public class GenesisJson { + + private String autor; + + private String coinbase; + + private String timestamp; + + private String perentHash; + + private String energonCeiling; + + private String difficulty; + + private String consensus; + + private String extraData; + + private Map accounts; + + private GenesisConfig jsonBlockchainConfig; + + public static class AccountJson { + public String balance; + } + + public String getAutor() { + return autor; + } + + public void setAutor(String autor) { + this.autor = autor; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getPerentHash() { + return perentHash; + } + + public void setPerentHash(String perentHash) { + this.perentHash = perentHash; + } + + public String getEnergonCeiling() { + return energonCeiling; + } + + public void setEnergonCeiling(String energonCeiling) { + this.energonCeiling = energonCeiling; + } + + public String getDifficulty() { + return difficulty; + } + + public void setDifficulty(String difficulty) { + this.difficulty = difficulty; + } + + public void setConsensus(String consensus) { + this.consensus = consensus; + } + + public String getConsensus() { + return consensus; + } + + public Map getAccounts() { + return accounts; + } + + public void setAccounts(Map accounts) { + this.accounts = accounts; + } + + public GenesisConfig getJsonBlockchainConfig() { + return jsonBlockchainConfig; + } + + public void setJsonBlockchainConfig(GenesisConfig jsonBlockchainConfig) { + this.jsonBlockchainConfig = jsonBlockchainConfig; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + public String getCoinbase() { + return coinbase; + } + + public void setCoinbase(String coinbase) { + this.coinbase = coinbase; + } +} diff --git a/core/src/main/java/org/platon/core/genesis/GenesisLoader.java b/core/src/main/java/org/platon/core/genesis/GenesisLoader.java new file mode 100644 index 0000000..d58e9a3 --- /dev/null +++ b/core/src/main/java/org/platon/core/genesis/GenesisLoader.java @@ -0,0 +1,114 @@ +package org.platon.core.genesis; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import org.platon.common.AppenderName; +import org.platon.common.utils.Numeric; +import org.platon.core.Repository; +import org.platon.core.block.GenesisBlock; +import org.platon.storage.trie.SecureTrie; +import org.platon.storage.trie.Trie; +import org.platon.storage.trie.TrieImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.math.BigInteger; +import java.util.Map; + +/** + * GenesisLoader + * + * @author yanze + * @desc load genesis file to json + * @create 2018-08-01 11:26 + **/ +public class GenesisLoader { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + public static GenesisJson loadGenesisJson(String genesisName) { + + String fileUrl = Thread.currentThread().getContextClassLoader().getResource("config/" + genesisName).getPath(); + File file = new File(fileUrl); + + String genesisJsonStr; + try { + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + StringBuffer stringBuffer = new StringBuffer(); + String line = ""; + while ((line = bufferedReader.readLine()) != null) { + stringBuffer.append(line); + } + bufferedReader.close(); + genesisJsonStr = stringBuffer.toString(); + return parseGenesisJsonStr(genesisJsonStr); + } catch (Exception e) { + logger.error("~> loadGenesisJson error: ", e); + return null; + } + } + + public static GenesisBlock loadGenesisBlock(Repository repository, GenesisJson genesisJson) { + GenesisBlock genesisBlock = parseGenesisJson(genesisJson); + genesisBlock.setAccounts(genesisJson.getAccounts()); + genesisBlock.getBlockHeader().setStateRoot(generateRootHash(genesisBlock.getAccounts())); + genesisBlock.resolve(repository); + return genesisBlock; + } + + public static byte[] generateRootHash(Map accounts){ + Trie state = new SecureTrie(); + for (String key : accounts.keySet()) { + state.put(Numeric.hexStringToByteArray(key), Numeric.hexStringToByteArray(accounts.get(key).balance)); + } + return state.getRootHash(); + } + + private static GenesisBlock parseGenesisJson(GenesisJson genesisJson) { + long timestamp = Long.parseLong(genesisJson.getTimestamp()); + byte[] author = Numeric.hexStringToByteArray(genesisJson.getAutor()); + byte[] parentHash = null != genesisJson.getPerentHash() ? + Hex.decode(Numeric.cleanHexPrefix(genesisJson.getPerentHash())) : new byte[]{}; + BigInteger energonCeiling = new BigInteger( + genesisJson.getEnergonCeiling() != null ? Numeric.cleanHexPrefix(genesisJson.getEnergonCeiling()) : "0"); + + BigInteger difficultyBI = new BigInteger( + null != genesisJson.getDifficulty() ? Numeric.cleanHexPrefix(genesisJson.getDifficulty()) : "0"); + byte[] extraData = genesisJson.getExtraData() != null ? + Hex.decode(Numeric.cleanHexPrefix(genesisJson.getExtraData())) : new byte[]{}; + byte[] coinbase = Numeric.hexStringToByteArray(genesisJson.getCoinbase()); + GenesisBlock genesisBlock = new GenesisBlock(timestamp, author,coinbase, parentHash, null, 0, + new BigInteger("0"), energonCeiling, difficultyBI, extraData); + + genesisBlock.getBlockHeader().setReceiptRoot(TrieImpl.EMPTY_TRIE_HASH); + genesisBlock.getBlockHeader().setPermissionRoot(TrieImpl.EMPTY_TRIE_HASH); + genesisBlock.getBlockHeader().setDposRoot(TrieImpl.EMPTY_TRIE_HASH); + genesisBlock.getBlockHeader().setVotingRoot(TrieImpl.EMPTY_TRIE_HASH); + genesisBlock.getBlockHeader().setTransactionRoot(TrieImpl.EMPTY_TRIE_HASH); + genesisBlock.getBlockHeader().setTransferRoot(TrieImpl.EMPTY_TRIE_HASH); + + return genesisBlock; + } + + private static GenesisJson parseGenesisJsonStr(String genesisJsonStr) { + GenesisJson genesisJson = new GenesisJson(); + Map genesisMap = JSON.parseObject(genesisJsonStr, new TypeReference>(){}); + genesisJson.setAutor(genesisMap.get("autor")); + genesisJson.setCoinbase(genesisMap.get("coinbase")); + genesisJson.setTimestamp(genesisMap.get("timestamp")); + genesisJson.setEnergonCeiling(genesisMap.get("energonCeiling")); + genesisJson.setDifficulty(genesisMap.get("difficulty")); + genesisJson.setPerentHash(genesisMap.get("perentHash")); + genesisJson.setExtraData(genesisMap.get("extraData")); + genesisJson.setAccounts(JSON.parseObject(genesisMap.get("accounts"), new TypeReference>() { + })); + genesisJson.setJsonBlockchainConfig(JSON.parseObject(genesisMap.get("config"), new TypeReference() { + })); + return genesisJson; + } + +} diff --git a/core/src/main/java/org/platon/core/keystore/AccountHolder.java b/core/src/main/java/org/platon/core/keystore/AccountHolder.java new file mode 100644 index 0000000..bfc71c6 --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/AccountHolder.java @@ -0,0 +1,49 @@ +package org.platon.core.keystore; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Manages real accounts + * + * @author alliswell + * @version 0.0.1 + * @date 2018/8/30 18:01 + */ +public class AccountHolder { + + @Autowired + private Keystore keystore; + + /** + * beneficiary of mining + */ + private byte[] miner; + + /** + * node's address + */ + private byte[] nodeAddress; + + public String[] accounts() { + return keystore.listStoredKeys(); + } + + public byte[] getMiner() { + if (null == miner || 0 == miner.length) { + miner = accounts()[0].getBytes(); + } + return miner; + } + + public void setMiner(byte[] miner) { + this.miner = miner; + } + + public byte[] getNodeAddress() { + return nodeAddress; + } + + public void setNodeAddress(byte[] nodeAddress) { + this.nodeAddress = nodeAddress; + } +} diff --git a/core/src/main/java/org/platon/core/keystore/CipherParams.java b/core/src/main/java/org/platon/core/keystore/CipherParams.java new file mode 100644 index 0000000..98bff9e --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/CipherParams.java @@ -0,0 +1,22 @@ +package org.platon.core.keystore; + +public class CipherParams { + + private String iv; + + public CipherParams() { + this(null); + } + + public CipherParams(String iv) { + this.iv = iv; + } + + public String getIv() { + return iv; + } + + public void setIv(String iv) { + this.iv = iv; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/keystore/FileSystemKeystore.java b/core/src/main/java/org/platon/core/keystore/FileSystemKeystore.java new file mode 100644 index 0000000..7def795 --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/FileSystemKeystore.java @@ -0,0 +1,141 @@ +package org.platon.core.keystore; + +import org.platon.common.AppenderName; +import org.platon.common.utils.Numeric; +import org.platon.core.config.CoreConfig; +import org.platon.crypto.ECKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +@Component +public class FileSystemKeystore implements Keystore { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_KEY_STORE); + + @Autowired + private CoreConfig config; + + public KeystoreFormat keystoreFormat = new KeystoreFormat(); + + @Override + public void removeKey(String address) { + getFiles().stream() + .filter(f -> hasAddressInName(address, f)) + .findFirst() + .ifPresent(f -> f.delete()); + } + + @Override + public void storeKey(ECKey key, String password) throws RuntimeException { + + final String address = Numeric.toHexString(key.getAddress()); + if (hasStoredKey(address)) { + throw new RuntimeException("Keystore is already exist for address: " + address + + " Please remove old one first if you want to add with new password."); + } + + final File keysFolder = getKeyStoreLocation().toFile(); + keysFolder.mkdirs(); + + String content = keystoreFormat.toKeystore(key, password); + storeRawKeystore(content, address); + } + + @Override + public void storeRawKeystore(String content, String address) throws RuntimeException { + + String fileName = "UTC--" + getISODate(System.currentTimeMillis()) + "--" + address; + try { + Files.write(getKeyStoreLocation().resolve(fileName), Arrays.asList(content)); + } catch (IOException e) { + throw new RuntimeException("Problem storing key for address"); + } + } + + @Override + public String[] listStoredKeys() { + return getFiles().stream() + .filter(f -> !f.isDirectory()) + .map(f -> f.getName().split("--")) + .filter(n -> n != null && n.length == 3) + .map(a -> a[2]) + .toArray(size -> new String[size]); + } + + @Override + public ECKey loadStoredKey(String address, String password) throws RuntimeException { + return getFiles().stream() + .filter(f -> hasAddressInName(address, f)) + .map(f -> { + try { + return Files.readAllLines(f.toPath()) + .stream() + .collect(Collectors.joining("")); + } catch (IOException e) { + throw new RuntimeException("Problem reading keystore file for address:" + address); + } + }) + .map(content -> keystoreFormat.fromKeystore(content, password)) + .findFirst() + .orElse(null); + } + + private boolean hasAddressInName(String address, File file) { + return !file.isDirectory() && file.getName().toLowerCase().endsWith("--" + address.toLowerCase()); + } + + @Override + public boolean hasStoredKey(String address) { + return getFiles().stream() + .filter(f -> hasAddressInName(address, f)) + .findFirst() + .isPresent(); + } + + private List getFiles() { + final File dir = getKeyStoreLocation().toFile(); + final File[] files = dir.listFiles(); + return files != null ? Arrays.asList(files) : Collections.emptyList(); + } + + private String getISODate(long milliseconds) { + + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm'Z'"); + df.setTimeZone(tz); + return df.format(new Date(milliseconds)); + } + + public Path getKeyStoreLocation() { + + String keystore = config.keystoreDir(); + + if (!StringUtils.isEmpty(keystore)) { + return Paths.get(keystore); + } + + final String keystoreDir = "keystore"; + final String osName = System.getProperty("os.name").toUpperCase(); + + if (osName.indexOf("WIN") >= 0) { + return Paths.get(System.getenv("APPDATA") + "/Platon/" + keystoreDir); + } else if (osName.indexOf("MAC") >= 0) { + return Paths.get(System.getProperty("user.home") + "/Library/Platon/" + keystoreDir); + } else { + return Paths.get(System.getProperty("user.home") + "/.platon/" + keystoreDir); + } + } +} diff --git a/core/src/main/java/org/platon/core/keystore/KdfParams.java b/core/src/main/java/org/platon/core/keystore/KdfParams.java new file mode 100644 index 0000000..8d91a5b --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/KdfParams.java @@ -0,0 +1,62 @@ +package org.platon.core.keystore; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class KdfParams { + + private Integer c; + private Integer dklen; + private String salt; + private Integer n; + private Integer p; + private Integer r; + + public Integer getN() { + return n; + } + + public Integer getR() { + return r; + } + + public void setR(Integer r) { + this.r = r; + } + + public void setN(Integer n) { + this.n = n; + } + + public Integer getP() { + return p; + } + + public void setP(Integer p) { + this.p = p; + } + + @JsonIgnore + public Integer getC() { + return c; + } + + public void setC(Integer c) { + this.c = c; + } + + public Integer getDklen() { + return dklen; + } + + public void setDklen(Integer dklen) { + this.dklen = dklen; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } +} diff --git a/core/src/main/java/org/platon/core/keystore/Keystore.java b/core/src/main/java/org/platon/core/keystore/Keystore.java new file mode 100644 index 0000000..c1d3a31 --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/Keystore.java @@ -0,0 +1,18 @@ +package org.platon.core.keystore; + +import org.platon.crypto.ECKey; + +public interface Keystore { + + void removeKey(String address); + + void storeKey(ECKey key, String password) throws RuntimeException; + + void storeRawKeystore(String content, String address) throws RuntimeException; + + String[] listStoredKeys(); + + ECKey loadStoredKey(String address, String password) throws RuntimeException; + + boolean hasStoredKey(String address); +} diff --git a/core/src/main/java/org/platon/core/keystore/KeystoreCrypto.java b/core/src/main/java/org/platon/core/keystore/KeystoreCrypto.java new file mode 100644 index 0000000..605da28 --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/KeystoreCrypto.java @@ -0,0 +1,75 @@ +package org.platon.core.keystore; + +public class KeystoreCrypto { + + private String cipher; + private String ciphertext; + private String kdf; + private String mac; + private CipherParams cipherparams; + private KdfParams kdfparams; + + public KeystoreCrypto() { + this(null, null, null, + null, null, null); + } + + public KeystoreCrypto( + String cipher, String ciphertext, String kdf, + String mac, CipherParams cipherparams, KdfParams kdfparams) { + this.cipher = cipher; + this.ciphertext = ciphertext; + this.kdf = kdf; + this.mac = mac; + this.cipherparams = cipherparams; + this.kdfparams = kdfparams; + } + + public String getCipher() { + return cipher; + } + + public void setCipher(String cipher) { + this.cipher = cipher; + } + + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + public String getKdf() { + return kdf; + } + + public void setKdf(String kdf) { + this.kdf = kdf; + } + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + public CipherParams getCipherparams() { + return cipherparams; + } + + public void setCipherparams(CipherParams cipherparams) { + this.cipherparams = cipherparams; + } + + public KdfParams getKdfparams() { + return kdfparams; + } + + public void setKdfparams(KdfParams kdfparams) { + this.kdfparams = kdfparams; + } +} diff --git a/core/src/main/java/org/platon/core/keystore/KeystoreFormat.java b/core/src/main/java/org/platon/core/keystore/KeystoreFormat.java new file mode 100644 index 0000000..7cc2c7d --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/KeystoreFormat.java @@ -0,0 +1,200 @@ +package org.platon.core.keystore; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.platon.common.AppenderName; +import org.platon.common.utils.Numeric; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.bouncycastle.crypto.generators.SCrypt; +import org.bouncycastle.jcajce.provider.digest.Keccak; +import org.bouncycastle.util.encoders.Hex; +import org.springframework.stereotype.Component; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; +import java.util.Arrays; +import java.util.UUID; + +@Component +public class KeystoreFormat { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_KEY_STORE); + + + public String toKeystore(final ECKey key, String password) { + + try { + + + final int ScryptN = 262144; + final int ScryptR = 8; + final int ScryptP = 1; + final int ScryptDklen = 32; + + final byte[] salt = generateRandomBytes(32); + + + final byte[] derivedKey = scrypt(password.getBytes(), salt, ScryptN, ScryptR, ScryptP, ScryptDklen); + + + final byte[] iv = generateRandomBytes(16); + final byte[] privateKey = key.getPrivKeyBytes(); + final byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); + final byte[] cipherText = encryptAes(iv, encryptKey, privateKey); + final byte[] mac = HashUtil.sha3(concat(Arrays.copyOfRange(derivedKey, 16, 32), cipherText)); + + + final KeystoreItem keystore = new KeystoreItem(); + keystore.address = Numeric.toHexString(key.getAddress()); + keystore.id = UUID.randomUUID().toString(); + keystore.version = 3; + keystore.crypto = new KeystoreCrypto(); + keystore.crypto.setKdf("scrypt"); + keystore.crypto.setMac(Hex.toHexString(mac)); + keystore.crypto.setCipher("aes-128-ctr"); + keystore.crypto.setCiphertext(Hex.toHexString(cipherText)); + keystore.crypto.setCipherparams(new CipherParams()); + keystore.crypto.getCipherparams().setIv(Hex.toHexString(iv)); + keystore.crypto.setKdfparams(new KdfParams()); + keystore.crypto.getKdfparams().setN(ScryptN); + keystore.crypto.getKdfparams().setR(ScryptR); + keystore.crypto.getKdfparams().setP(ScryptP); + keystore.crypto.getKdfparams().setDklen(ScryptDklen); + keystore.crypto.getKdfparams().setSalt(Hex.toHexString(salt)); + + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(keystore); + } catch (Exception e) { + logger.error("Problem storing key", e); + throw new RuntimeException("Problem storing key. Message: " + e.getMessage(), e); + } + } + + private byte[] generateRandomBytes(int size) { + final byte[] bytes = new byte[size]; + SecureRandom random = new SecureRandom(); + random.nextBytes(bytes); + return bytes; + } + + + public ECKey fromKeystore(final String content, final String password) { + ObjectMapper mapper = new ObjectMapper(); + + try { + final KeystoreItem keystore = mapper.readValue(content, KeystoreItem.class); + final byte[] cipherKey; + + if (keystore.version != 3) { + throw new RuntimeException("Keystore version 3 only supported."); + } + + switch (keystore.getCrypto().getKdf()) { + case "pbkdf2": + cipherKey = checkMacSha3(keystore, password); + break; + case "scrypt": + cipherKey = checkMacScrypt(keystore, password); + break; + default: + throw new RuntimeException("non valid algorithm " + keystore.getCrypto().getCipher()); + } + + byte[] privateKey = decryptAes( + Hex.decode(keystore.getCrypto().getCipherparams().getIv()), + cipherKey, + Hex.decode(keystore.getCrypto().getCiphertext()) + ); + + return ECKey.fromPrivate(privateKey); + } catch (Exception e) { + throw new RuntimeException("Cannot unlock account. Message: " + e.getMessage(), e); + } + } + + private byte[] decryptAes(byte[] iv, byte[] keyBytes, byte[] cipherText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + return processAes(iv, keyBytes, cipherText, Cipher.DECRYPT_MODE); + } + + private byte[] encryptAes(byte[] iv, byte[] keyBytes, byte[] cipherText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + return processAes(iv, keyBytes, cipherText, Cipher.ENCRYPT_MODE); + } + + private byte[] processAes(byte[] iv, byte[] keyBytes, byte[] cipherText, int encryptMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + + cipher.init(encryptMode, key, ivSpec); + return cipher.doFinal(cipherText); + } + + private byte[] checkMacSha3(KeystoreItem keystore, String password) throws Exception { + byte[] salt = Hex.decode(keystore.getCrypto().getKdfparams().getSalt()); + int iterations = keystore.getCrypto().getKdfparams().getC(); + byte[] part = new byte[16]; + byte[] h = hash(password, salt, iterations); + byte[] cipherText = Hex.decode(keystore.getCrypto().getCiphertext()); + System.arraycopy(h, 16, part, 0, 16); + + byte[] actual = sha3(concat(part, cipherText)); + + if (Arrays.equals(actual, Hex.decode(keystore.getCrypto().getMac()))) { + System.arraycopy(h, 0, part, 0, 16); + return part; + } + + throw new RuntimeException("Most probably a wrong passphrase"); + } + + private byte[] checkMacScrypt(KeystoreItem keystore, String password) throws Exception { + byte[] part = new byte[16]; + KdfParams params = keystore.getCrypto().getKdfparams(); + byte[] h = scrypt(password.getBytes(), Hex.decode(params.getSalt()), params.getN(), params.getR(), params.getP(), params.getDklen()); + byte[] cipherText = Hex.decode(keystore.getCrypto().getCiphertext()); + System.arraycopy(h, 16, part, 0, 16); + + byte[] actual = sha3(concat(part, cipherText)); + + if (Arrays.equals(actual, Hex.decode(keystore.getCrypto().getMac()))) { + System.arraycopy(h, 0, part, 0, 16); + return part; + } + + throw new RuntimeException("Most probably a wrong passphrase"); + } + + private byte[] concat(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] c = new byte[aLen + bLen]; + System.arraycopy(a, 0, c, 0, aLen); + System.arraycopy(b, 0, c, aLen, bLen); + return c; + } + + private byte[] scrypt(byte[] pass, byte[] salt, int n, int r, int p, int dkLen) throws GeneralSecurityException { + return SCrypt.generate(pass, salt, n, r, p, dkLen); + } + + private byte[] hash(String encryptedData, byte[] salt, int iterations) throws Exception { + char[] chars = encryptedData.toCharArray(); + PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 256); + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + return skf.generateSecret(spec).getEncoded(); + } + + private byte[] sha3(byte[] h) throws NoSuchAlgorithmException { + MessageDigest KECCAK = new Keccak.Digest256(); + KECCAK.reset(); + KECCAK.update(h); + return KECCAK.digest(); + } +} diff --git a/core/src/main/java/org/platon/core/keystore/KeystoreItem.java b/core/src/main/java/org/platon/core/keystore/KeystoreItem.java new file mode 100644 index 0000000..21f193c --- /dev/null +++ b/core/src/main/java/org/platon/core/keystore/KeystoreItem.java @@ -0,0 +1,49 @@ +package org.platon.core.keystore; + +import org.codehaus.jackson.annotate.JsonSetter; + +public class KeystoreItem { + + public KeystoreCrypto crypto; + public String id; + public Integer version; + public String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public KeystoreCrypto getCrypto() { + return crypto; + } + + @JsonSetter("crypto") + public void setCrypto(KeystoreCrypto crypto) { + this.crypto = crypto; + } + + @JsonSetter("Crypto") + public void setCryptoOld(KeystoreCrypto crypto) { + this.crypto = crypto; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } +} diff --git a/core/src/main/java/org/platon/core/listener/CompoplexPlatonListener.java b/core/src/main/java/org/platon/core/listener/CompoplexPlatonListener.java new file mode 100644 index 0000000..ffe9e29 --- /dev/null +++ b/core/src/main/java/org/platon/core/listener/CompoplexPlatonListener.java @@ -0,0 +1,129 @@ +package org.platon.core.listener; + +import org.platon.core.block.Block; +import org.platon.core.BlockSummary; +import org.platon.core.EventDispatchWorker; +import org.platon.core.PendingStateIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +@Component(value = "platonListener") +public class CompoplexPlatonListener implements PlatonListener { + + @Autowired + private EventDispatchWorker eventDispatchThread = EventDispatchWorker.getDefault(); + + private List listeners = new CopyOnWriteArrayList<>(); + + + public void addListener(PlatonListener listener) { + listeners.add(listener); + } + + + public void removeListener(PlatonListener listener) { + listeners.remove(listener); + } + + @Override + public void trace(final String output) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "trace") { + @Override + public void run() { + listener.trace(output); + } + }); + } + } + + @Override + public void onBlock(final BlockSummary blockSummary) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onBlock") { + @Override + public void run() { + listener.onBlock(blockSummary); + } + }); + } + } + + @Override + public void onPeerDisconnect(final String host, final long port) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onPeerDisconnect") { + @Override + public void run() { + listener.onPeerDisconnect(host, port); + } + }); + } + } + + @Override + public void onPendingTransactionsReceived(final List transactions) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onPendingTransactionsReceived") { + @Override + public void run() { + listener.onPendingTransactionsReceived(transactions); + } + }); + } + } + + @Override + public void onPendingStateChanged(final PendingStateIfc pendingState) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onPendingStateChanged") { + @Override + public void run() { + listener.onPendingStateChanged(pendingState); + } + }); + } + } + + @Override + public void onNoConnections() { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onNoConnections") { + @Override + public void run() { + listener.onNoConnections(); + } + }); + } + } + + @Override + public void onVMTraceCreated(final String transactionHash, final String trace) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onVMTraceCreated") { + @Override + public void run() { + listener.onVMTraceCreated(transactionHash, trace); + } + }); + } + } + + @Override + public void onPendingTransactionUpdate(final TransactionReceipt txReceipt, final PendingTransactionState state, + final Block block) { + for (final PlatonListener listener : listeners) { + eventDispatchThread.invokeLater(new RunnableWrapper(listener, "onPendingTransactionUpdate") { + @Override + public void run() { + listener.onPendingTransactionUpdate(txReceipt, state, block); + } + }); + } + } +} diff --git a/core/src/main/java/org/platon/core/listener/PendingTransactionState.java b/core/src/main/java/org/platon/core/listener/PendingTransactionState.java new file mode 100644 index 0000000..296c5c3 --- /dev/null +++ b/core/src/main/java/org/platon/core/listener/PendingTransactionState.java @@ -0,0 +1,42 @@ +package org.platon.core.listener; + +public enum PendingTransactionState { + + /** + * Transaction may be dropped due to: + * - Invalid transaction (invalid nonce, low gas price, insufficient account funds, + * invalid signature) + * - Timeout (when pending transaction is not included to any block for + * last [transaction.outdated.threshold] blocks + * This is the final state + */ + DROPPED, + + /** + * The same as PENDING when transaction is just arrived + * Next state can be either PENDING or INCLUDED + */ + NEW_PENDING, + + /** + * State when transaction is not included to any blocks (on the main chain), and + * was executed on the last best block. The repository state is reflected in the PendingState + * Next state can be either INCLUDED, DROPPED (due to timeout) + * or again PENDING when a new block (without this transaction) arrives + */ + PENDING, + + /** + * State when the transaction is included to a block. + * This could be the final state, however next state could also be + * PENDING: when a fork became the main chain but doesn't include this tx + * INCLUDED: when a fork became the main chain and tx is included into another + * block from the new main chain + * DROPPED: If switched to a new (long enough) main chain without this Tx + */ + INCLUDED; + + public boolean isPending() { + return this == NEW_PENDING || this == PENDING; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/listener/PlatonListener.java b/core/src/main/java/org/platon/core/listener/PlatonListener.java new file mode 100644 index 0000000..150cb41 --- /dev/null +++ b/core/src/main/java/org/platon/core/listener/PlatonListener.java @@ -0,0 +1,48 @@ +package org.platon.core.listener; + +import org.platon.core.block.Block; +import org.platon.core.BlockSummary; +import org.platon.core.PendingStateIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; + +import java.util.List; + +public interface PlatonListener { + + void trace(String output); + + void onBlock(BlockSummary blockSummary); + + void onPeerDisconnect(String host, long port); + + void onPendingTransactionsReceived(List transactions); + + /** + * PendingState changes on either new pending transaction or new best block receive + * When a new transaction arrives it is executed on top of the current pending state + * When a new best block arrives the PendingState is adjusted to the new Repository state + * and all transactions which remain pending are executed on top of the new PendingState + */ + void onPendingStateChanged(PendingStateIfc pendingState); + + /** + * Is called when PendingTransaction arrives, executed or dropped and included to a block + * + * @param txReceipt Receipt of the tx execution on the current PendingState + * @param state Current state of pending tx + * @param block The block which the current pending state is based on (for PENDING tx state) + * or the block which tx was included to (for INCLUDED state) + */ + void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block); + + //void onSyncDone(SyncState state); + + void onNoConnections(); + + void onVMTraceCreated(String transactionHash, String trace); + + //todo: 待确定 + //void onTransactionExecuted(TransactionExecutionSummary summary); + +} diff --git a/core/src/main/java/org/platon/core/listener/PlatonListenerAdapter.java b/core/src/main/java/org/platon/core/listener/PlatonListenerAdapter.java new file mode 100644 index 0000000..0032202 --- /dev/null +++ b/core/src/main/java/org/platon/core/listener/PlatonListenerAdapter.java @@ -0,0 +1,52 @@ +package org.platon.core.listener; + +import org.platon.core.block.Block; +import org.platon.core.BlockSummary; +import org.platon.core.PendingStateIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; + +import java.util.List; + +public class PlatonListenerAdapter implements PlatonListener { + + @Override + public void trace(String output) { + + } + + @Override + public void onBlock(BlockSummary blockSummary) { + + } + + @Override + public void onPeerDisconnect(String host, long port) { + + } + + @Override + public void onPendingTransactionsReceived(List transactions) { + + } + + @Override + public void onPendingStateChanged(PendingStateIfc pendingState) { + + } + + @Override + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + + } + + @Override + public void onNoConnections() { + + } + + @Override + public void onVMTraceCreated(String transactionHash, String trace) { + + } +} diff --git a/core/src/main/java/org/platon/core/listener/RunnableWrapper.java b/core/src/main/java/org/platon/core/listener/RunnableWrapper.java new file mode 100644 index 0000000..8b8d6b7 --- /dev/null +++ b/core/src/main/java/org/platon/core/listener/RunnableWrapper.java @@ -0,0 +1,17 @@ +package org.platon.core.listener; + +public abstract class RunnableWrapper implements Runnable { + + private PlatonListener listener; + private String info; + + public RunnableWrapper(PlatonListener listener, String info) { + this.listener = listener; + this.info = info; + } + + @Override + public String toString() { + return "RunnableWrapper: " + info + " [listener: " + listener.getClass() + "]"; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/manager/InitialManager.java b/core/src/main/java/org/platon/core/manager/InitialManager.java new file mode 100644 index 0000000..af7cd43 --- /dev/null +++ b/core/src/main/java/org/platon/core/manager/InitialManager.java @@ -0,0 +1,147 @@ +package org.platon.core.manager; + +import org.platon.common.AppenderName; +import org.platon.common.utils.Numeric; +import org.platon.core.*; +import org.platon.core.block.Block; +import org.platon.core.block.GenesisBlock; +import org.platon.core.config.CoreConfig; +import org.platon.core.config.SystemConfig; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.db.DbFlushManager; +import org.platon.core.listener.CompoplexPlatonListener; +import org.platon.core.listener.PlatonListener; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.storage.trie.TrieImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +/** + * + * @author - Jungle + * @date 2018/9/6 11:02 + * @version 0.0.1 + */ +@Component +public class InitialManager { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + + @Autowired + private PendingStateIfc pendingState; + + @Autowired + private EventDispatchWorker eventDispatchThread; + + @Autowired + private DbFlushManager dbFlushManager; + + @Autowired + private ApplicationContext ctx; + + private CoreConfig config; + + private PlatonListener listener; + + private Blockchain blockchain; + + private Repository repository; + + private BlockStoreIfc blockStore; + + private SystemConfig systemConfig; + + @Autowired + public InitialManager(final CoreConfig config, final Repository repository, + final PlatonListener listener, final Blockchain blockchain, + final BlockStoreIfc blockStore, final SystemConfig systemConfig) { + this.listener = listener; + this.blockchain = blockchain; + this.repository = repository; + this.blockStore = blockStore; + this.config = config; + this.systemConfig = systemConfig; + loadBlockchain(); + } + + public void addListener(PlatonListener listener) { + logger.info("~> Platon listener added success."); + ((CompoplexPlatonListener) this.listener).addListener(listener); + } + + public PlatonListener getListener() { + return listener; + } + + public Repository getRepository() { + return (Repository) repository; + } + + public Blockchain getBlockchain() { + return blockchain; + } + + public BlockStoreIfc getBlockStore() { + return blockStore; + } + + public PendingStateIfc getPendingState() { + return pendingState; + } + + public void loadBlockchain() { + + if (blockStore.getBestBlock() == null) { + + logger.info("~> DB is empty - adding Genesis"); + GenesisBlock genesis = systemConfig.getGenesisBlock(repository); + repository.commit(); + + blockStore.saveBlock(genesis, genesis.getBlockHeader().getDifficulty(), true); + blockchain.setBestBlock(genesis); + blockchain.setTotalDifficulty(genesis.getBlockHeader().getDifficulty()); + + listener.onBlock(new BlockSummary(genesis, new HashMap(), + new ArrayList(), new ArrayList())); + + logger.info("~> Load Genesis block complete..."); + + } else { + Block bestBlock = blockStore.getBestBlock(); + + blockchain.setBestBlock(bestBlock); + BigInteger totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlock.getBlockHeader().getHash()); + blockchain.setTotalDifficulty(totalDifficulty); + + logger.info("~> Loaded up to block [{}] totalDifficulty [{}] with stateRoot [{}]", + blockchain.getBestBlock().getBlockHeader().getNumber(), + blockchain.getTotalDifficulty().toString(), + Numeric.toHexString(blockchain.getBestBlock().getBlockHeader().getStateRoot())); + } + if (!Arrays.equals(blockchain.getBestBlock().getBlockHeader().getStateRoot(), TrieImpl.EMPTY_TRIE_HASH)) { + this.repository.syncToRoot(blockchain.getBestBlock().getBlockHeader().getStateRoot()); + } + } + + + + public void close() { + logger.info("~> close: shutting down event dispatch thread used by EventBus ..."); + eventDispatchThread.shutdown(); + logger.info("~> close: closing Blockchain instance ..."); + blockchain.close(); + logger.info("~> close: closing main repository ..."); + repository.close(); + logger.info("~> close: database flush manager ..."); + dbFlushManager.close(); + } + +} diff --git a/core/src/main/java/org/platon/core/mine/BlockProduceManager.java b/core/src/main/java/org/platon/core/mine/BlockProduceManager.java new file mode 100644 index 0000000..8569268 --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/BlockProduceManager.java @@ -0,0 +1,348 @@ +package org.platon.core.mine; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import org.apache.commons.collections4.CollectionUtils; +import org.platon.common.AppenderName; +import org.platon.core.Blockchain; +import org.platon.core.ImportResult; +import org.platon.core.PendingStateIfc; +import org.platon.core.PendingStateImpl; +import org.platon.core.block.Block; +import org.platon.core.block.BlockHeader; +import org.platon.core.config.CoreConfig; +import org.platon.core.consensus.ConsensusManager; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.facade.Platon; +import org.platon.core.facade.PlatonImpl; +import org.platon.core.listener.CompoplexPlatonListener; +import org.platon.core.transaction.Transaction; +import org.platon.core.utils.TransactionSortedSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.*; + +@Component +public class BlockProduceManager { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_MINE); + + private static ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Autowired + private Platon platon; + + private Blockchain blockchain; + private BlockStoreIfc blockStore; + private ConsensusManager consensusManager; + protected PendingStateIfc pendingState; + private CompoplexPlatonListener listener; + private CoreConfig config; + + // + private List listeners = new CopyOnWriteArrayList<>(); + + private BigInteger minEnergonPrice; + private long minBlockTimeout; + + // for pow alg + private int cpuThreads; + private boolean fullMining = true; + + private volatile boolean isLocalMining; + private Block miningBlock; + + // 挖矿算法 + private volatile MinerAlgorithmIfc externalMiner; + private Thread minerBlockThread; + + private final Queue> currentMiningTasks = new ConcurrentLinkedQueue<>(); + + private long lastBlockMinedTime; + private int UNCLE_LIST_LIMIT; + private int UNCLE_GENERATION_LIMIT; + + @Autowired + public BlockProduceManager(final CoreConfig config, final CompoplexPlatonListener listener, + final Blockchain blockchain, final BlockStoreIfc blockStore, + final PendingStateIfc pendingState, final ConsensusManager consensusManager) { + this.listener = listener; + this.config = config; + this.blockchain = blockchain; + this.blockStore = blockStore; + this.pendingState = pendingState; + this.consensusManager = consensusManager; + UNCLE_LIST_LIMIT = 10; + UNCLE_GENERATION_LIMIT = 10; + minEnergonPrice = new BigInteger("15000000000"); // 15Gwei + + minBlockTimeout = 0; + cpuThreads = 4; + fullMining = true; + + // tips: 启动挖矿主线程 + Runnable mineTask = this::processMineBlock; + minerBlockThread = new Thread(mineTask, "ProduceBlockThread"); + minerBlockThread.start(); + } + + private void processMineBlock() { + + if (logger.isDebugEnabled()) { + logger.debug("Produce block thread start..."); + } + + while (!Thread.currentThread().isInterrupted()) { + + try { + // tips: 提取pending交易,判断是否可以进行挖矿. + if (!consensusManager.shouldSeal()) { + continue; + } + if (null == miningBlock) { + if (logger.isDebugEnabled()) { + logger.debug("~> Restart mining: mining block is null."); + } + } else if (miningBlock.getBlockHeader().getNumber() <= ((PendingStateImpl) pendingState).getBestBlock().getBlockHeader().getNumber()) { + if (logger.isDebugEnabled()) { + logger.debug("~> Restart mining: new best block: " + blockchain.getBestBlock().getShortDescr()); + } + } else if (!CollectionUtils.isEqualCollection(miningBlock.getTransactions(), getAllPendingTransactions())) { + if (logger.isDebugEnabled()) { + logger.debug("~> Restart mining: pending transactions changed"); + } + } else { + if (logger.isTraceEnabled()) { + String s = "~> onPendingStateChanged() event, but pending Txs the same as in currently mining block: "; + for (Transaction tx : getAllPendingTransactions()) { + s += "\n " + tx; + } + logger.trace(s); + } + } + startMining(); + } catch (Exception e) { + if (e instanceof InterruptedException) { + break; + } + logger.error("~> Error mine block.", e); + } + } + } + + public void setFullMining(boolean fullMining) { + this.fullMining = fullMining; + } + + public void setCpuThreads(int cpuThreads) { + this.cpuThreads = cpuThreads; + } + + public void setMinEnergonPrice(BigInteger minEnergonPrice) { + this.minEnergonPrice = minEnergonPrice; + } + + public void setExternalMiner(MinerAlgorithmIfc miner) { + externalMiner = miner; + } + + public void startMining() { + isLocalMining = true; + fireMinerStarted(); // fire listener + logger.info("Miner started..."); + restartMining(); + } + + public void stopMining() { + isLocalMining = false; + cancelCurrentBlock(); + fireMinerStopped(); + logger.info("Miner stopped"); + } + + /** + * @return 返回pendingTxs,从pendingState的池中提取. + */ + protected List getAllPendingTransactions() { + TransactionSortedSet ret = new TransactionSortedSet(); + ret.addAll(pendingState.getPendingTransactions()); + Iterator it = ret.iterator(); + while (it.hasNext()) { + Transaction tx = it.next(); + if (!isAcceptableTx(tx)) { + if (logger.isDebugEnabled()) { + logger.debug("Producer excluded the transaction: {}", tx); + } + it.remove(); + } + } + return new ArrayList<>(ret); + } + + protected boolean isAcceptableTx(Transaction tx) { + return minEnergonPrice.compareTo(tx.getEnergonPrice()) <= 0; + } + + // tips: 取消当前正出块任务 + protected synchronized void cancelCurrentBlock() { + for (ListenableFuture task : currentMiningTasks) { + if (task != null && !task.isCancelled()) { + task.cancel(true); + } + } + currentMiningTasks.clear(); + + if (miningBlock != null) { + fireBlockCancelled(miningBlock); + logger.debug("Tainted block mining cancelled: {}", miningBlock.getShortDescr()); + miningBlock = null; + } + } + + protected List getUncles(Block mineBest) { + //todo: 叔区块是否也存在? + List ret = new ArrayList<>(); + return ret; + } + + /** + * @return 打包了交易的区块,需要结合共识算法出块. + */ + protected Block getNewBlockForMining() { + Block bestBlockChainBlock = blockchain.getBestBlock(); + Block bestPendingStateBlock = ((PendingStateImpl) pendingState).getBestBlock(); + if (null == bestBlockChainBlock) return null; + if (logger.isDebugEnabled()) { + logger.debug("~> Get new block for produce, best blocks : PendingState: " + + (null == bestPendingStateBlock ? "" : bestPendingStateBlock.getShortDescr() ) + + ", BlockChain : " + (null == bestBlockChainBlock ? "" : bestBlockChainBlock.getShortDescr())); + } + + // tips: 打包交易,生成一个区块. + Block newMiningBlock = blockchain.createNewBlock(bestPendingStateBlock, getAllPendingTransactions(), getUncles(bestPendingStateBlock)); + return newMiningBlock; + } + + protected void restartMining() { + + Block newMiningBlock = getNewBlockForMining(); + if (newMiningBlock == null) { + if (logger.isDebugEnabled()) { + logger.debug("~> newMiningBlock is null."); + } + return; + } + synchronized (this) { + cancelCurrentBlock(); + miningBlock = newMiningBlock; + + // tips: 挖矿动作交由共识主线程去完成, + if (consensusManager != null && isLocalMining) { + consensusManager.setListeners(listeners); + currentMiningTasks.add(consensusManager.mine(cloneBlock(miningBlock))); + } + + for (final ListenableFuture task : currentMiningTasks) { + task.addListener(() -> { + try { + // 哇, 挖到宝了,出块产生 + final Block minedBlock = task.get().block; + blockMined(minedBlock); + } catch (InterruptedException | CancellationException e) { + // OK, we've been cancelled, just exit + } catch (Exception e) { + logger.warn("Exception during mining: ", e); + } + }, MoreExecutors.directExecutor()); + } + } + fireBlockStarted(newMiningBlock); + logger.debug("New block mining started: {}", newMiningBlock.getShortHash()); + } + + /** + * Block cloning is required before passing block to concurrent miner env. + * In success result miner will modify this block instance. + */ + private Block cloneBlock(Block block) { + return new Block(block.encode()); + } + + protected void blockMined(Block newBlock) throws InterruptedException { + long t = System.currentTimeMillis(); + if (t - lastBlockMinedTime < minBlockTimeout) { + long sleepTime = minBlockTimeout - (t - lastBlockMinedTime); + logger.debug("~> Last block was mined " + (t - lastBlockMinedTime) + " ms ago. Sleeping " + + sleepTime + " ms before importing..."); + Thread.sleep(sleepTime); + } + + fireBlockMined(newBlock); + logger.info("~> Wow, block mined !!!: {}", newBlock.toString()); + + lastBlockMinedTime = t; + miningBlock = null; + // cancel all tasks + cancelCurrentBlock(); + + // broadcast the block + logger.debug("~> Importing newly mined block {} {} ...", newBlock.getShortHash(), newBlock.getBlockHeader().getNumber()); + ImportResult importResult = ((PlatonImpl) platon).addNewMinedBlock(newBlock); + logger.debug("~> Mined block import result is " + importResult); + } + + public MinerAlgorithmIfc getConsensusAlgorithm(CoreConfig config) { + // tips: 依据配置读取不同的共识算法实现 + return new NoMinerAlgorithm(); + } + + public boolean isMining() { + return isLocalMining || externalMiner != null; + } + + public void addListener(MinerListener l) { + listeners.add(l); + } + + public void removeListener(MinerListener l) { + listeners.remove(l); + } + + protected void fireMinerStarted() { + for (MinerListener l : listeners) { + l.miningStarted(); + } + } + + protected void fireMinerStopped() { + for (MinerListener l : listeners) { + l.miningStopped(); + } + } + + protected void fireBlockStarted(Block b) { + for (MinerListener l : listeners) { + l.blockMiningStarted(b); + } + } + + protected void fireBlockCancelled(Block b) { + for (MinerListener l : listeners) { + l.blockMiningCanceled(b); + } + } + + protected void fireBlockMined(Block b) { + for (MinerListener l : listeners) { + l.blockMined(b); + } + } +} diff --git a/core/src/main/java/org/platon/core/mine/CompositeFuture.java b/core/src/main/java/org/platon/core/mine/CompositeFuture.java new file mode 100644 index 0000000..0b0f2fa --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/CompositeFuture.java @@ -0,0 +1,57 @@ +package org.platon.core.mine; + +import com.google.common.util.concurrent.AbstractFuture; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.ArrayList; +import java.util.List; + +public class CompositeFuture extends AbstractFuture { + + private List> futures = new ArrayList<>(); + + public synchronized void add(final ListenableFuture f) { + + if (isCancelled() || isDone()) return; + + f.addListener(() -> futureCompleted(f), MoreExecutors.directExecutor()); + futures.add(f); + } + + private synchronized void futureCompleted(ListenableFuture f) { + if (isCancelled() || isDone()) return; + if (f.isCancelled()) return; + + try { + cancelOthers(f); + V v = f.get(); + postProcess(v); + set(v); + } catch (Exception e) { + setException(e); + } + } + + /** + * Subclasses my override to perform some task on the calculated + * value before returning it via Future + */ + protected void postProcess(V v) {} + + private void cancelOthers(ListenableFuture besidesThis) { + for (ListenableFuture future : futures) { + if (future != besidesThis) { + try { + future.cancel(true); + } catch (Exception e) { + } + } + } + } + + @Override + protected void interruptTask() { + cancelOthers(null); + } +} diff --git a/core/src/main/java/org/platon/core/mine/MineTask.java b/core/src/main/java/org/platon/core/mine/MineTask.java new file mode 100644 index 0000000..dac58fc --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/MineTask.java @@ -0,0 +1,37 @@ +package org.platon.core.mine; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import org.platon.core.block.Block; + +import java.util.concurrent.Callable; + +public class MineTask extends CompositeFuture { + + private Block block; + private int nThreads; + private Callable miner; + private ListeningExecutorService executor; + + public MineTask(Block block, int nThreads, Callable miner, + ListeningExecutorService executor) { + this.block = block; + this.nThreads = nThreads; + this.miner = miner; + this.executor = executor; + } + + public MineTask submit() { + for (int i = 0; i < nThreads; i++) { + ListenableFuture f = executor.submit(miner); + add(f); + } + return this; + } + + @Override + protected void postProcess(MiningResult result) { + + + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/mine/MinerAlgorithmIfc.java b/core/src/main/java/org/platon/core/mine/MinerAlgorithmIfc.java new file mode 100644 index 0000000..5b50132 --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/MinerAlgorithmIfc.java @@ -0,0 +1,43 @@ +package org.platon.core.mine; + +import com.google.common.util.concurrent.ListenableFuture; +import org.platon.core.block.BlockHeader; +import org.platon.core.block.Block; + +import java.util.Collection; + +/** + * @author - Jungle + * @date 2018/9/5 10:55 + * @version 0.0.1 + */ +public interface MinerAlgorithmIfc { + + /** + * Starts mining the block. On successful mining the Block is update with necessary nonce and hash. + * @return MiningResult Future object. The mining can be canceled via this Future. The Future is complete + * when the block successfully mined. + */ + ListenableFuture mine(Block block); + + /** + * Validates the Proof of Work for the block + */ + boolean validate(BlockHeader blockHeader); + + /** + * Passes {@link MinerListener}'s to miner + */ + void setListeners(Collection listeners); + + boolean shouldSeal(); + + byte[] headHash(); + + byte[] currentIrbHash(); + + byte[] lastIrbHash(); + + void generateSeal(BlockHeader header); + +} diff --git a/core/src/main/java/org/platon/core/mine/MinerListener.java b/core/src/main/java/org/platon/core/mine/MinerListener.java new file mode 100644 index 0000000..6334819 --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/MinerListener.java @@ -0,0 +1,17 @@ +package org.platon.core.mine; + +import org.platon.core.block.Block; + + +public interface MinerListener { + + void miningStarted(); + + void miningStopped(); + + void blockMiningStarted(Block block); + + void blockMined(Block block); + + void blockMiningCanceled(Block block); +} diff --git a/core/src/main/java/org/platon/core/mine/MiningResult.java b/core/src/main/java/org/platon/core/mine/MiningResult.java new file mode 100644 index 0000000..0458181 --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/MiningResult.java @@ -0,0 +1,30 @@ +package org.platon.core.mine; + +import org.platon.core.block.Block; + +public class MiningResult { + + public final long nonce; + + public final byte[] digest; + + public final Block block; + + public MiningResult(long nonce, byte[] digest, Block block) { + this.nonce = nonce; + this.digest = digest; + this.block = block; + } + + public long getNonce() { + return nonce; + } + + public byte[] getDigest() { + return digest; + } + + public Block getBlock() { + return block; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/mine/NoMinerAlgorithm.java b/core/src/main/java/org/platon/core/mine/NoMinerAlgorithm.java new file mode 100644 index 0000000..36becde --- /dev/null +++ b/core/src/main/java/org/platon/core/mine/NoMinerAlgorithm.java @@ -0,0 +1,75 @@ +package org.platon.core.mine; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.platon.core.block.BlockHeader; +import org.platon.core.block.Block; + +import java.util.Collection; +import java.util.concurrent.Callable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class NoMinerAlgorithm implements MinerAlgorithmIfc { + + private static ListeningExecutorService executor = MoreExecutors.listeningDecorator( + new ThreadPoolExecutor(8, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder().setNameFormat("hash-pool-%d").build())); + + private int cpuThreads; + private boolean fullMining = true; + + public NoMinerAlgorithm() { + cpuThreads = 4; + fullMining = true; + } + + @Override + public ListenableFuture mine(Block block) { + + return new MineTask(block, 1, new Callable() { + @Override + public MiningResult call() throws Exception { + return new MiningResult(0, new byte[]{0}, block); + } + }, executor).submit(); + } + + @Override + public boolean validate(BlockHeader blockHeader) { + return blockHeader != null; + } + + @Override + public void setListeners(Collection listeners) { + + } + + @Override + public boolean shouldSeal() { + return true; + } + + @Override + public byte[] headHash() { + throw new UnsupportedOperationException("Not Supported."); + } + + @Override + public byte[] currentIrbHash() { + throw new UnsupportedOperationException("Not Supported."); + } + + @Override + public byte[] lastIrbHash() { + throw new UnsupportedOperationException("Not Supported."); + } + + @Override + public void generateSeal(BlockHeader header) { + throw new UnsupportedOperationException("Not Supported."); + } +} diff --git a/core/src/main/java/org/platon/core/rpc/ProtoRpc.java b/core/src/main/java/org/platon/core/rpc/ProtoRpc.java new file mode 100644 index 0000000..8e22b8b --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/ProtoRpc.java @@ -0,0 +1,62 @@ +package org.platon.core.rpc; + +import org.platon.core.exception.OperationException; +import org.platon.slice.message.request.TransactionBaseRequest; +import org.platon.slice.message.response.*; + + +public interface ProtoRpc { + + String atp4Sha3(String data); + + String atpProtocolVersion(); + + SyncingResultResponse atpSyncing(); + + BoolResponse atpMining(); + + StringArrayResponse atpAccounts(); + + StringResponse atpBlockNumber(); + + StringResponse atpGetBalance(String address, String blockId); + + StringResponse atpGetLastBalance(String address); + + StringResponse atpGetBlockTransactionCountByHash(String blockHash); + + StringResponse atpGetBlockTransactionCountByNumber(String bnOrId); + + StringResponse atpSign(String addr, String data); + + StringResponse atpSendTransaction(TransactionBaseRequest txRequest) throws OperationException; + + StringResponse atpSendFillTransaction(TransactionBaseRequest txRequest); + + + StringResponse atpCall(TransactionBaseRequest txRequest); + + BlockResponse atpGetBlockByHash(String blockHash, Boolean fullTransactionObjects); + + BlockResponse atpGetBlockByNumber(String bnOrId, Boolean fullTransactionObjects); + + TransactionResponse atpGetTransactionByHash(String transactionHash); + + TransactionResponse atpGetTransactionByBlockHashAndIndex(String blockHash, String index); + + TransactionResponse atpGetTransactionByBlockNumberAndIndex(String bnOrId, String index); + + TransactionReceiptResponse atpGetTransactionReceipt(String txHash); + + StringResponse personalNewAccount(String password); + + StringResponse personalImportRawKey(String keydata, String passphrase); + + BoolResponse personalUnlockAccount(String addr, String pass, String duration); + + BoolResponse personalLockAccount(String address); + + StringArrayResponse personalListAccounts(); + + StringResponse personalSignAndSendTransaction(TransactionBaseRequest tx, String password); +} diff --git a/core/src/main/java/org/platon/core/rpc/ProtoRpcImpl.java b/core/src/main/java/org/platon/core/rpc/ProtoRpcImpl.java new file mode 100644 index 0000000..b8912b1 --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/ProtoRpcImpl.java @@ -0,0 +1,538 @@ +package org.platon.core.rpc; + +import com.google.protobuf.ByteString; +import org.platon.common.AppenderName; +import org.platon.common.utils.ByteUtil; +import org.platon.common.utils.Numeric; +import org.platon.core.TransactionInfo; +import org.platon.core.block.BlockHeader; +import org.platon.core.config.Constants; +import org.platon.core.exception.OperationException; +import org.platon.core.exception.PlatonException; +import org.platon.core.keystore.Keystore; +import org.platon.core.block.Block; +import org.platon.core.facade.Platon; +import org.platon.core.rpc.model.AccountModel; +import org.platon.core.transaction.LogInfo; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.core.transaction.proto.TransactionBody; +import org.platon.core.transaction.proto.TransactionType; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.platon.slice.message.request.TransactionBaseRequest; +import org.platon.slice.message.response.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class ProtoRpcImpl implements ProtoRpc { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_RPC); + + @Autowired + private Keystore keystore; + + @Autowired + private Platon platon; + + private static final String BLOCK_LATEST = "latest"; + + final Map addresses = new ConcurrentHashMap<>(); + + final Map unlockedAccounts = new ConcurrentHashMap<>(); + + @Override + public String atp4Sha3(String data) { + byte[] result = HashUtil.sha3(data.getBytes()); + return Numeric.toHexString(result); + } + + @Override + public String atpProtocolVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public SyncingResultResponse atpSyncing() { + throw new UnsupportedOperationException(); + } + + @Override + public BoolResponse atpMining() { + throw new UnsupportedOperationException(); + } + + @Override + public StringArrayResponse atpAccounts() { + String[] accounts = keystore.listStoredKeys(); + return convertArrayToResponse(accounts); + } + + @Override + public StringResponse atpBlockNumber() { + if (logger.isDebugEnabled()) { + logger.debug("into atpBlockNumber."); + } + //long blockNumber = this.platon.getBestBlock().info().getNumber(); + long blockNumber = 0; + return convertStrToResponse(Numeric.toHexStringWithPrefix(BigInteger.valueOf(blockNumber))); + } + + @Override + public StringResponse atpGetBalance(String address, String blockId) { + + Objects.requireNonNull(address, "address is required."); + blockId = null == blockId ? BLOCK_LATEST : blockId; + byte[] addrAsByteArray = Numeric.hexStringToByteArray(address); + + //BigInteger balance = platon.getBalance(addrAsByteArray); + BigInteger balance = BigInteger.ZERO; + return convertStrToResponse(Numeric.toHexStringWithPrefix(balance)); + } + + @Override + public StringResponse atpGetLastBalance(String address) { + return atpGetBalance(address, BLOCK_LATEST); + } + + @Override + public StringResponse atpGetBlockTransactionCountByHash(String blockHash) { + final byte[] bbHash = Numeric.hexStringToByteArray(blockHash); + Block block = platon.getBlockchain().getBlockByHash(bbHash); + if(null == block) { + return null; + } + long size = block.getTransactions().size(); + String hexSize = Numeric.toHexStringWithPrefixSafe(BigInteger.valueOf(size)); + return StringResponse.newBuilder().setData(hexSize).build(); + } + + private Block getBlockByHexHash(String blockHash) { + return null; + } + + @Override + public StringResponse atpGetBlockTransactionCountByNumber(String bnOrId) { + + if (logger.isDebugEnabled()) { + logger.debug("into atpGetBlockTransactionCountByNumber, the bnOrId is : {}", bnOrId); + } + List list = getTransactionsByHexBlockId(bnOrId); + if (list == null) return null; + long n = list.size(); + return convertStrToResponse(Numeric.toHexStringWithPrefix(BigInteger.valueOf(n))); + } + + private List getTransactionsByHexBlockId(String id) { + Block block = getBlockByHexBlockId(id); + return block != null ? block.getTransactions() : null; + } + + private Block getBlockByHexBlockId(String id) { + return null; + } + + @Override + public StringResponse atpSign(String addr, String data) { + + return null; + } + + @Override + public StringResponse atpSendTransaction(TransactionBaseRequest txRequest) throws OperationException { + if (logger.isDebugEnabled()) { + logger.debug("into atpSendTransaction , txRequest : {} ", txRequest); + } + if (txRequest.getBody() == null) return null; + AccountModel account = getAccountFromKeystore(txRequest.getFrom()); + return sendTransaction(txRequest, account); + } + + private StringResponse sendTransaction(TransactionBaseRequest txRequest, AccountModel account) { + + byte[] valueBytes = txRequest.getBody().getValue().toByteArray(); + final BigInteger valueBigInt = valueBytes != null ? Numeric.toBigInt(valueBytes) : BigInteger.ZERO; + final byte[] value = null == valueBytes ? ByteUtil.EMPTY_BYTE_ARRAY : valueBytes; + + TransactionBody body = txRequest.getBody(); + TransactionType type = body.getType(); + + final Transaction tx = new Transaction( + type, + valueBigInt, + body.getReceiveAddress() != null ? body.getReceiveAddress().toByteArray() : ByteUtil.EMPTY_BYTE_ARRAY, + body.getReferenceBlockNum(), + body.getReferenceBlockHash().toByteArray(), + Numeric.toBigInt(body.getEnergonPrice().toByteArray()), + body.getEnergonLimit() != null ? Numeric.toBigInt(body.getEnergonLimit().toByteArray()) : BigInteger.valueOf(90_000), + body.getData() != null ? body.getData().toByteArray() : ByteUtil.EMPTY_BYTE_ARRAY + ); + + // sign message + tx.sign(account.getEcKey()); + + validateAndSubmit(tx); + + return convertStrToResponse(Numeric.toHexString(tx.getHash())); + } + + @Override + public StringResponse atpSendFillTransaction(TransactionBaseRequest txRequest) { + + if (logger.isDebugEnabled()) { + logger.debug("into atpSendFillTransaction."); + } + if (null == txRequest.getBody()) throw new IllegalArgumentException("Invalid request params."); + + // TransactionBaseRequest -> TransactionProto + try { + org.platon.core.transaction.proto.Transaction pbTx = convertToTxProto(txRequest); + Transaction tx = new Transaction(pbTx.toByteArray()); + tx.protoParse(); + validateAndSubmit(tx); + // tips: for test + //String sender = Numeric.toHexString(tx.getSender()); + + return convertStrToResponse(Numeric.toHexString(tx.getHash())); + } catch (Exception e) { + logger.error("atpSendFillTransaction throw exception.", e); + throw new RuntimeException(e.getMessage()); + } + } + + protected void validateAndSubmit(Transaction tx) { + platon.submitTransaction(tx); + } + + @Override + public StringResponse atpCall(TransactionBaseRequest txRequest) { + + return null; + } + + @Override + public BlockResponse atpGetBlockByHash(String blockHash, Boolean fullTransactionObjects) { + final Block b = getBlockByHexHash(blockHash); + return getBlockResponse(b, fullTransactionObjects); + } + + @Override + public BlockResponse atpGetBlockByNumber(String bnOrId, Boolean fullTransactionObjects) { + + final Block b = getBlockByHexBlockId(bnOrId); + ; + return (b == null ? null : getBlockResponse(b, fullTransactionObjects)); + } + + @Override + public TransactionResponse atpGetTransactionByHash(String transactionHash) { + + if (logger.isTraceEnabled()) { + logger.trace("into atpGetTransactionByHash(), the transactionHash:{}", transactionHash); + } + + final byte[] txHash = Numeric.hexStringToByteArray(transactionHash); + + final TransactionInfo txInfo = this.platon.getTransactionInfo(txHash); + if(txInfo == null){ + return null; + } + final Block block = getBlockByHexHash(Numeric.toHexString(txInfo.getBlockHash())); + final Block mainBlock = getBlockByHexBlockId(Numeric.toHexStringWithPrefix(BigInteger.valueOf(block.getBlockHeader().getNumber()))); + + if(!Arrays.equals(block.getBlockHeader().getHash(),mainBlock.getBlockHeader().getHash())){ + return null; + } + + // convert to transactionResponse + txInfo.setTransaction(block.getTransactions().get(txInfo.getIndex())); + return getTxResponse(block, txInfo.getIndex(), txInfo.getReceipt().getTransaction()); + } + + @Override + public TransactionResponse atpGetTransactionByBlockHashAndIndex(String blockHash, String index) { + + if (logger.isDebugEnabled()) { + logger.debug("into atpGetTransactionByBlockHashAndIndex , the blockHash : {}, the index: {}", blockHash, index); + } + Block b = getBlockByHexHash(blockHash); + if (b == null) return null; + int idx = Numeric.toBigInt(index).intValue(); + if (idx >= b.getTransactions().size()) return null; + Transaction tx = b.getTransactions().get(idx); + return getTxResponse(b, idx, tx); + } + + @Override + public TransactionResponse atpGetTransactionByBlockNumberAndIndex(String bnOrId, String index) { + + if (logger.isDebugEnabled()) { + logger.debug("into atpGetTransactionByBlockNumberAndIndex, the bnOrId:{} , the index : {} .", bnOrId, index); + } + Block b = getBlockByHexBlockId(bnOrId); + List txs = getTransactionsByHexBlockId(bnOrId); + if (txs == null) return null; + int _index = Numeric.toBigInt(index).intValue(); + if (_index >= txs.size()) return null; + Transaction tx = txs.get(_index); + return getTxResponse(b, _index, tx); + } + + @Override + public TransactionReceiptResponse atpGetTransactionReceipt(String txHash) { + + if (logger.isTraceEnabled()) { + logger.trace("into atpGetTransactionReceipt , the txHash : {} .", txHash); + } + final byte[] hash = Numeric.hexStringToByteArray(txHash); + final TransactionInfo txInfo = platon.getTransactionInfo(hash); + if(null == txInfo) { + return null; + } + + final Block block = getBlockByHexHash(Numeric.toHexString(txInfo.getBlockHash())); + final Block mainBlock = getBlockByHexBlockId(Numeric.toHexStringWithPrefix(BigInteger.valueOf(block.getBlockHeader().getNumber()))); + + if(!Arrays.equals(block.getBlockHeader().getHash(),mainBlock.getBlockHeader().getHash())){ + return null; + } + + return getTxReceiptResponse(block, txInfo); + } + + @Override + public StringResponse personalNewAccount(String password) { + if (logger.isTraceEnabled()) { + logger.trace("into personalNewAccount.."); + } + ECKey key = new ECKey(); + final AccountModel account = new AccountModel(); + account.init(key); + final String address = Numeric.toHexString(account.getAddress()); + int i = 1; + boolean vacant = false; + String name = null; + while (!vacant) { + name = String.format("Account #%s", i); + if (!addresses.values().contains(name)) { + vacant = true; + } else { + ++i; + } + } + keystore.storeKey(key, password); + addresses.put(address, name); + return convertStrToResponse(address); + } + + @Override + public StringResponse personalImportRawKey(String keydata, String passphrase) { + + throw new UnsupportedOperationException(); + } + + @Override + public BoolResponse personalUnlockAccount(String addr, String pass, String duration) { + + if (logger.isDebugEnabled()) { + logger.debug("into personalUnlockAccount , the addr : {}, the pass : {} , the duration : {}", + addr, pass, duration); + } + Objects.requireNonNull(addr, "addr is required."); + Objects.requireNonNull(pass, "password is required."); + + final ECKey key = keystore.loadStoredKey(addr, pass); + if (null == key) { + throw new PlatonException("No key was found in keystore for account : " + addr); + } else { + logger.info("Found key address is " + Numeric.toHexString(key.getAddress())); + final AccountModel model = new AccountModel(); + model.init(key); + logger.info("Found account address is " + Numeric.toHexString(model.getAddress())); + unlockedAccounts.put(Numeric.toHexString(model.getAddress()).toLowerCase(), model); + return BoolResponse.newBuilder().setData(true).build(); + } + } + + @Override + public BoolResponse personalLockAccount(String address) { + + if (logger.isDebugEnabled()) { + logger.debug("into personalLockAccount , the address : {} ", address); + } + Objects.requireNonNull(address, "address is required."); + unlockedAccounts.remove(address.toLowerCase()); + + return BoolResponse.newBuilder().setData(true).build(); + } + + @Override + public StringArrayResponse personalListAccounts() { + + if (logger.isDebugEnabled()) { + logger.debug("into personalListAccounts."); + } + + String[] keys = keystore.listStoredKeys(); + return convertArrayToResponse(keys); + } + + @Override + public StringResponse personalSignAndSendTransaction(TransactionBaseRequest tx, String password) { + + if (logger.isDebugEnabled()) { + logger.debug("into personalSignAndSendTransaction."); + } + + Objects.requireNonNull(password, "password is required."); + final ECKey key = keystore.loadStoredKey(tx.getFrom().toLowerCase(), password); + if (null == key) throw new PlatonException("No key was found in keystore for account : " + tx.getFrom()); + final AccountModel model = new AccountModel(); + model.init(key); + return sendTransaction(tx, model); + } + + public StringArrayResponse convertArrayToResponse(String[] element) { + StringArrayResponse.Builder builder = StringArrayResponse.newBuilder(); + Arrays.stream(element).forEach(str -> builder.addData(str)); + return builder.build(); + } + + private StringResponse convertStrToResponse(String element) { + StringResponse.Builder build = StringResponse.newBuilder(); + build.setData(element); + return build.build(); + } + + protected AccountModel getAccountFromKeystore(String address) throws OperationException{ + + final AccountModel model = unlockedAccounts.get(address.toLowerCase()); + if (model != null) { + return model; + } + + if (keystore.hasStoredKey(address)) { + throw new OperationException("Unlocked account is required. Account: " + address, Constants.ERROR__1001_UNLOCK_ACCOUNT); + } else { + throw new OperationException("Key not found in keystore", Constants.ERROR__1002_KEY_NOT_FOUND); + } + } + + private org.platon.core.transaction.proto.Transaction convertToTxProto(TransactionBaseRequest txRequest) { + + org.platon.core.transaction.proto.Transaction.Builder txBuilder = + org.platon.core.transaction.proto.Transaction.newBuilder(); + txBuilder.setBody(txRequest.getBody()); + if (null != txRequest.getSignature()) { + txBuilder.setSignature(ByteString.copyFrom(txRequest.getSignature().getBytes())); + } + return txBuilder.build(); + } + + protected BlockResponse getBlockResponse(Block block, boolean fullTx) { + if (block == null) + return null; + //TODO: pow 时 nonce 有意义 + BlockResponse.Builder bloBuild = BlockResponse.newBuilder(); + + BlockHeader bh = block.getBlockHeader(); + if (null == bh) return null; + + bloBuild.setBlockNumber(block.getBlockHeader().getNumber()); + if (null != bh.getHash()) bloBuild.setHash(Numeric.toHexString(block.getBlockHeader().getHash())); + if (null != bh.getParentHash()) + bloBuild.setParentHash(Numeric.toHexString(block.getBlockHeader().getParentHash())); + if (bh.getBloomLog() != null) bloBuild.setLogsBloom(Numeric.toHexString(bh.getBloomLog())); + if (null != bh.getTransactionRoot()) bloBuild.setTransactionsRoot(Numeric.toHexString(bh.getTransactionRoot())); + if (null != bh.getStateRoot()) bloBuild.setStateRoot(Numeric.toHexString(bh.getStateRoot())); + if (null != bh.getReceiptRoot()) bloBuild.setReceiptsRoot(Numeric.toHexString(bh.getReceiptRoot())); + if (null != bh.getAuthor()) bloBuild.setMiner(Numeric.toHexString(bh.getAuthor())); + + bloBuild.setSize(block.getBlockSize()); + if (null != bh.getExtraData()) bloBuild.setExtraData(Numeric.toHexString(bh.getExtraData())); + if (null != bh.getEnergonCeiling()) + bloBuild.setEnergonLimit(ByteString.copyFrom(bh.getEnergonCeiling().toByteArray())); + if (null != bh.getEnergonUsed()) + bloBuild.setEnergonUsed(ByteString.copyFrom(bh.getEnergonUsed().toByteArray())); + bloBuild.setTimestamp(bh.getTimestamp()); + + // txs + List txs = block.getTransactions(); + if (null != txs) { + for (int i = 0; i < txs.size(); i++) { + bloBuild.setTransactions(i, Numeric.toHexString(txs.get(i).getHash())); + } + } + + return bloBuild.build(); + } + + protected TransactionResponse getTxResponse(Block b, int txIndex, Transaction tx) { + + TransactionResponse.Builder txBuilder = TransactionResponse.newBuilder(); + txBuilder.setHash(Numeric.toHexString(tx.getHash())); + txBuilder.setBlockHash(Numeric.toHexString(b.getBlockHeader().getHash())); + txBuilder.setBlockNumber(b.getBlockHeader().getNumber()); + txBuilder.setTransactionIndex(txIndex); + txBuilder.setFrom(Numeric.toHexString(tx.getSender())); + if (null != tx.getReceiveAddress()) txBuilder.setTo(Numeric.toHexString(tx.getReceiveAddress())); + txBuilder.setValue(ByteString.copyFrom(tx.getValue().toByteArray())); + txBuilder.setEnergonPrice(ByteString.copyFrom(tx.getEnergonPrice().toByteArray())); + txBuilder.setEnergonLimit(ByteString.copyFrom(tx.getEnergonLimit().toByteArray())); + txBuilder.setInput(Numeric.toHexString(tx.getData())); + return txBuilder.build(); + } + + protected TransactionReceiptResponse getTxReceiptResponse(Block b, TransactionInfo txInfo) { + + if (null == txInfo) return null; + TransactionReceipt txReceipt = txInfo.getReceipt(); + + TransactionReceiptResponse.Builder txReceiptBuild = TransactionReceiptResponse.newBuilder(); + txReceiptBuild.setTransactionhash(Numeric.toHexString(txReceipt.getTransaction().getHash())); + txReceiptBuild.setTransactionIndex(txInfo.getIndex()); + + if (null != b) { + txReceiptBuild.setBlockHash(Numeric.toHexString(b.getBlockHeader().getHash())); + txReceiptBuild.setBlockNumber(b.getBlockHeader().getNumber()); + } + txReceiptBuild.setFrom(Numeric.toHexString(txReceipt.getTransaction().getSender())); + txReceiptBuild.setTo(Numeric.toHexString(txReceipt.getTransaction().getReceiveAddress())); + //if(null != txReceipt.getTransaction().get){} + + // logs + List logInfos = txReceipt.getLogInfoList(); + if (null != logInfos) { + for (int i = 0; i < logInfos.size(); i++) { + LogInfo logInfo = logInfos.get(i); + + LogEntry.Builder logBuilder = LogEntry.newBuilder(); + logBuilder.setBlockHash(Numeric.toHexString(b.getBlockHeader().getHash())); + logBuilder.setAddress(Numeric.toHexString(logInfo.getAddress())); + logBuilder.setLogIndex(Numeric.toHexStringWithPrefix(BigInteger.valueOf(i))); + logBuilder.setData(Numeric.toHexString(logInfo.getData())); + + for (int n = 0; n < logInfo.getTopics().size(); n++) { + logBuilder.setTopics(n, Numeric.toHexString(logInfo.getTopics().get(n).getData())); + } + if (b != null) logBuilder.setBlockNumber(b.getBlockHeader().getNumber()); + logBuilder.setTransactionIndex(txInfo.getIndex()); + logBuilder.setTransactionHash(Numeric.toHexString(txReceipt.getTransaction().getHash())); + txReceiptBuild.setLogs(i, logBuilder.build()); + } + } + + return txReceiptBuild.build(); + } + +} diff --git a/core/src/main/java/org/platon/core/rpc/Response.java b/core/src/main/java/org/platon/core/rpc/Response.java new file mode 100644 index 0000000..f0e4970 --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/Response.java @@ -0,0 +1,77 @@ +package org.platon.core.rpc; + + +import com.google.protobuf.Any; +import org.platon.slice.message.ResultCodeMessage; +import org.platon.slice.message.response.BaseResponse; + +public class Response { + + private static final Response DEFAULT_INSTANCE; + private static BaseResponse.Builder response = null; + + public enum ErrorCode { + + Unsupported(95271, "Not Supported."), + ; + + private int code ; + private String reason; + + public int getCode(){ + return this.code; + } + + public String getReason(){ + return this.reason; + } + + ErrorCode(int code, String reason) { + this.code = code; + this.reason = reason; + } + } + + static { + DEFAULT_INSTANCE = new Response(); + } + + public static Response newResponse() { + return DEFAULT_INSTANCE; + } + + public static Response newResponse(ResultCodeMessage.ResultCode code, String msg, Any data) { + response = BaseResponse.newBuilder(); + DEFAULT_INSTANCE.withData(data); + DEFAULT_INSTANCE.withMsg(msg); + DEFAULT_INSTANCE.withResultCode(code); + return DEFAULT_INSTANCE; + } + + public static Response newResponse(ResultCodeMessage.ResultCode code, String msg) { + response = BaseResponse.newBuilder(); + DEFAULT_INSTANCE.withMsg(msg); + DEFAULT_INSTANCE.withResultCode(code); + return DEFAULT_INSTANCE; + } + + public Response withResultCode(ResultCodeMessage.ResultCode code){ + response.setCode(code); + return this; + } + + public Response withMsg(String msg){ + response.setMsg(msg); + return this; + } + + public Response withData(Any data){ + response.setData(data); + return this; + } + + public BaseResponse build(){ + return response.build(); + } + +} diff --git a/core/src/main/java/org/platon/core/rpc/model/AccountModel.java b/core/src/main/java/org/platon/core/rpc/model/AccountModel.java new file mode 100644 index 0000000..ecd5906 --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/model/AccountModel.java @@ -0,0 +1,92 @@ +package org.platon.core.rpc.model; + +import org.platon.core.Repository; +import org.platon.core.transaction.Transaction; +import org.platon.crypto.ECKey; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +@Component +@Scope("prototype") +public class AccountModel { + + private ECKey ecKey; + private byte[] address; + + private Set pendingTransactions = + Collections.synchronizedSet(new HashSet()); + + @Autowired + private Repository repository; + + public AccountModel() { + } + + public void init() { + this.ecKey = new ECKey(); + address = this.ecKey.getAddress(); + } + + public void init(ECKey ecKey) { + this.ecKey = ecKey; + address = this.ecKey.getAddress(); + } + + public BigInteger getBalance() { + + BigInteger balance = repository.getBalance(this.getAddress()); + + synchronized (getPendingTransactions()) { + if (!getPendingTransactions().isEmpty()) { + + for (Transaction tx : getPendingTransactions()) { + if (Arrays.equals(getAddress(), tx.getSender())) { + balance = balance.subtract(tx.getValue()); + } + + if (Arrays.equals(getAddress(), tx.getReceiveAddress())) { + balance = balance.add(tx.getValue()); + } + } + } + } + return balance; + } + + + public ECKey getEcKey() { + return ecKey; + } + + public byte[] getAddress() { + return address; + } + + public void setAddress(byte[] address) { + this.address = address; + } + + public Set getPendingTransactions() { + return this.pendingTransactions; + } + + public void addPendingTransaction(Transaction transaction) { + synchronized (pendingTransactions) { + pendingTransactions.add(transaction); + } + } + + public void clearAllPendingTransactions() { + synchronized (pendingTransactions) { + pendingTransactions.clear(); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/rpc/servant/AtpGrpcServiceImpl.java b/core/src/main/java/org/platon/core/rpc/servant/AtpGrpcServiceImpl.java new file mode 100644 index 0000000..fae4761 --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/servant/AtpGrpcServiceImpl.java @@ -0,0 +1,326 @@ +package org.platon.core.rpc.servant; + +import com.google.protobuf.Any; +import io.grpc.stub.StreamObserver; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.core.exception.OperationException; +import org.platon.core.rpc.ProtoRpc; +import org.platon.core.rpc.Response; +import org.platon.service.grpc.PlatonServiceGrpc; +import org.platon.slice.message.ResultCodeMessage; +import org.platon.slice.message.request.*; +import org.platon.slice.message.response.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; + +public class AtpGrpcServiceImpl extends PlatonServiceGrpc.PlatonServiceImplBase { + + private Logger logger = LoggerFactory.getLogger(AtpGrpcServiceImpl.class); + + private ProtoRpc protoRpc; + + public AtpGrpcServiceImpl(){ + protoRpc = SpringContextUtil.getBean(ProtoRpc.class); + } + + @Override + public void atpCall(TransactionBaseRequest request, StreamObserver responseObserver) { + throw new RuntimeException("Unsupported operation."); + } + + @Override + public void atpSendTransaction(TransactionBaseRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpSendTransaction, the txRequest : {}", request); + } + + try { + StringResponse resp = this.protoRpc.atpSendTransaction(request); + if(null == resp){ + throw new OperationException("get null for call atpSendTransaction ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpSendTransaction.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } catch (Exception e) { + logger.error("Throw exception.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, "system exception.").build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetTransactionReceiptByHash(GetTransactionReceiptByHashRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetTransactionReceiptByHash, the txHash : {}", request.getTxHash()); + } + String txHash = request.getTxHash(); + try { + TransactionReceiptResponse resp = this.protoRpc.atpGetTransactionReceipt(txHash); + int sleepDuration = 200; + int attempts = 15; + for (int i = 0; i < attempts; i++) { + if (null == resp) { + try { + Thread.sleep(sleepDuration); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + resp = this.protoRpc.atpGetTransactionReceipt(txHash); + } else { + break; + } + } + if(null == resp) { + throw new OperationException("cound not get receipt after (" + (sleepDuration * attempts) + ") seconds."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetTransactionReceipt.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpSendFillTransaction(TransactionBaseRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpSendFillTransaction, the txRequest : {}", request); + } + + try { + if(null == request.getSignature()) throw new OperationException("Signature required"); + if(null == request.getFrom()) throw new OperationException("From required."); + + StringResponse resp = this.protoRpc.atpSendFillTransaction(request); + if(null == resp){ + throw new OperationException("get null for call atpSendFillTransaction ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (Exception e) { + logger.error("throw exception for call protoRpc.atpSendFillTransaction.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpEnergonPrice(VoidRequest request, StreamObserver responseObserver) { + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, + "ok", Any.pack(StringResponse.newBuilder().setData("0x11").build())) + .build()); + responseObserver.onCompleted(); + } + + @Override + public void atpGetTransactionCount(GetTransactionCountRequest request, StreamObserver responseObserver) { + throw new RuntimeException("Unsupported operation."); + } + + @Override + public void atpProtocolVersion(VoidRequest request, StreamObserver responseObserver) { + throw new RuntimeException("Unsupported operation."); + } + + @Override + public void atpCoinbase(VoidRequest request, StreamObserver responseObserver) { + throw new RuntimeException("Unsupported operation."); + } + + @Override + public void atpAccounts(VoidRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpAccounts."); + } + try { + StringArrayResponse resp = this.protoRpc.atpAccounts(); + if(null == resp){ + throw new OperationException("get null for call atpAccounts ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpAccounts.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpBlockNumber(VoidRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpBlockNumber."); + } + try { + StringResponse resp = this.protoRpc.atpBlockNumber(); + if(null == resp){ + throw new OperationException("get null for call atpBlockNumber ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpBlockNumber.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetBalance(GetBalanceRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetBalance. address : {}, blockParams : {}", request.getAddress(), request.getBlockParams()); + } + try { + StringResponse resp = this.protoRpc.atpGetBalance(request.getAddress(), request.getBlockParams()); + if(null == resp){ + throw new OperationException("get null for call atpGetBalance ."); + } + responseObserver.onNext( Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetBalance.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetBlockTransactionCountByHash(GetBlockTransactionCountByHashRequest request, StreamObserver responseObserver) { + if (logger.isTraceEnabled()) { + logger.trace("Proto Request : atpGetBlockTransactionCountByHash(). blockHash:{}", request.getBlockHash()); + } + try { + StringResponse resp = this.protoRpc.atpGetBlockTransactionCountByHash(request.getBlockHash()); + if (resp == null) { + throw new OperationException("Get null for call atpGetBlockTransactionCountByHash."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)).build()); + }catch (Exception e){ + logger.error("throw exception for call protoRpc.atpGetBlockTransactionCountByHash.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetBlockTransactionCountByNumber(GetBlockTransactionCountByNumberRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetBlockTransactionCountByNumber. blockNumber : {}.", request.getBlockNumber()); + } + try { + StringResponse resp = this.protoRpc.atpGetBlockTransactionCountByNumber(request.getBlockNumber()); + if(null == resp){ + throw new OperationException("get null for call atpGetBlockTransactionCountByNumber ."); + } + responseObserver.onNext( Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok",Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetBlockTransactionCountByNumber.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetBlockByHash(GetBlockByHashRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetBlockByHash. blockHash : {}.", request.getBlockHash()); + } + try { + BlockResponse resp = this.protoRpc.atpGetBlockByHash(request.getBlockHash(),true); + if(null == resp){ + throw new OperationException("get null for call atpGetBlockByHash ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetBlockByHash.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetBlockByNumber(GetBlockByNumberRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetBlockByNumber. blockNumber : {}.", request.getBlockNumber()); + } + try { + BlockResponse resp = this.protoRpc.atpGetBlockByNumber(request.getBlockNumber(),true); + if(null == resp){ + throw new OperationException("get null for call atpGetBlockByNumber ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetBlockByNumber.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetTransactionByHash(GetTransactionByHashRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetTransactionByHash. txHash : {}.", request.getTxHash()); + } + try { + TransactionResponse resp = this.protoRpc.atpGetTransactionByHash(request.getTxHash()); + if(null == resp){ + throw new OperationException("get null for call atpGetTransactionByHash ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetTransactionByHash.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } + + @Override + public void atpGetTransactionByBlockHashAndIndex(GetTransactionByBlockHashAndIndexRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetTransactionByBlockHashAndIndex. blockHash : {}, index : {}" + , request.getBlockHash(), request.getIndex()); + } + try { + TransactionResponse resp = this.protoRpc.atpGetTransactionByBlockHashAndIndex(request.getBlockHash(),Numeric.toHexStringWithPrefix(BigInteger.valueOf(request.getIndex()))); + if(null == resp){ + throw new OperationException("get null for call atpGetTransactionByBlockHashAndIndex ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetTransactionByBlockHashAndIndex.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + } + + @Override + public void atpGetTransactionByBlockNumberAndIndex(GetTransactionByBlockNumberAndIndexRequest request, StreamObserver responseObserver) { + if (logger.isDebugEnabled()) { + logger.debug("Proto Request : atpGetTransactionByBlockNumberAndIndex. blockNumber : {}, index : {}" + , request.getBlockNumber(), request.getIndex()); + } + try { + TransactionResponse resp = this.protoRpc.atpGetTransactionByBlockNumberAndIndex(request.getBlockNumber(),Numeric.toHexStringWithPrefix(BigInteger.valueOf(request.getIndex()))); + if(null == resp){ + throw new OperationException("get null for call atpGetTransactionByBlockNumberAndIndex ."); + } + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.SUCCESS, "ok", Any.pack(resp)) + .build()); + } catch (OperationException e) { + logger.error("throw exception for call protoRpc.atpGetTransactionByBlockNumberAndIndex.", e); + responseObserver.onNext(Response.newResponse(ResultCodeMessage.ResultCode.FAIL, e.getMessage()).build()); + } + responseObserver.onCompleted(); + } +} diff --git a/core/src/main/java/org/platon/core/rpc/wallet/FileSystemWalletStore.java b/core/src/main/java/org/platon/core/rpc/wallet/FileSystemWalletStore.java new file mode 100644 index 0000000..fe2f622 --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/wallet/FileSystemWalletStore.java @@ -0,0 +1,63 @@ +package org.platon.core.rpc.wallet; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; +import org.platon.common.AppenderName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class FileSystemWalletStore { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_WALLET); + + private static String FILE_NAME = "wallet.json"; + + public void toStore(List addresses) { + try { + ObjectMapper mapper = new ObjectMapper(); + final String content = mapper.writeValueAsString(addresses); + + getKeyStoreLocation().toFile().mkdirs(); + Files.write(getKeyStoreLocation().resolve(FILE_NAME), Arrays.asList(content)); + } catch (Exception e) { + logger.error("Problem storing data", e); + throw new RuntimeException("Problem storing data. Message: " + e.getMessage(), e); + } + } + + public List fromStore() { + final ObjectMapper mapper = new ObjectMapper(); + + try { + final File file = getKeyStoreLocation().resolve(FILE_NAME).toFile(); + + final String content = Files.readAllLines(file.toPath()) + .stream() + .collect(Collectors.joining("")); + + return mapper.readValue(content, new TypeReference>() { + }); + } catch (NoSuchFileException e) { + return Collections.EMPTY_LIST; + } catch (IOException e) { + throw new RuntimeException("Problem loading data. Message: " + e.getMessage(), e); + } + } + + protected Path getKeyStoreLocation() { + return Paths.get("walletstore"); + } +} diff --git a/core/src/main/java/org/platon/core/rpc/wallet/WalletAddressItem.java b/core/src/main/java/org/platon/core/rpc/wallet/WalletAddressItem.java new file mode 100644 index 0000000..26b8eea --- /dev/null +++ b/core/src/main/java/org/platon/core/rpc/wallet/WalletAddressItem.java @@ -0,0 +1,47 @@ +package org.platon.core.rpc.wallet; + +public class WalletAddressItem { + + public String address; + + public String name; + + public WalletAddressItem(String address, String name) { + this.address = address; + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WalletAddressItem that = (WalletAddressItem) o; + + if (getAddress() != null ? !getAddress().equals(that.getAddress()) : that.getAddress() != null) return false; + return getName() != null ? getName().equals(that.getName()) : that.getName() == null; + } + + @Override + public int hashCode() { + int result = getAddress() != null ? getAddress().hashCode() : 0; + result = 31 * result + (getName() != null ? getName().hashCode() : 0); + return result; + } +} diff --git a/core/src/main/java/org/platon/core/transaction/Bloom.java b/core/src/main/java/org/platon/core/transaction/Bloom.java new file mode 100644 index 0000000..b590dba --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/Bloom.java @@ -0,0 +1,81 @@ +package org.platon.core.transaction; + +import org.platon.core.transaction.util.ByteUtil; + +import java.util.Arrays; + +public class Bloom { + + public static final long MEM_SIZE = 256 + 16; + + final static int _8STEPS = 8; + final static int _3LOW_BITS = 7; + final static int ENSURE_BYTE = 255; + + byte[] data = new byte[256]; + + + public Bloom() { + } + + public Bloom(byte[] data) { + this.data = data; + } + + public static Bloom create(byte[] toBloom) { + + int mov1 = (((toBloom[0] & ENSURE_BYTE) & (_3LOW_BITS)) << _8STEPS) + ((toBloom[1]) & ENSURE_BYTE); + int mov2 = (((toBloom[2] & ENSURE_BYTE) & (_3LOW_BITS)) << _8STEPS) + ((toBloom[3]) & ENSURE_BYTE); + int mov3 = (((toBloom[4] & ENSURE_BYTE) & (_3LOW_BITS)) << _8STEPS) + ((toBloom[5]) & ENSURE_BYTE); + + byte[] data = new byte[256]; + Bloom bloom = new Bloom(data); + + ByteUtil.setBit(data, mov1, 1); + ByteUtil.setBit(data, mov2, 1); + ByteUtil.setBit(data, mov3, 1); + + return bloom; + } + + public void or(Bloom bloom) { + for (int i = 0; i < data.length; ++i) { + data[i] |= bloom.data[i]; + } + } + + public boolean matches(Bloom topicBloom) { + Bloom copy = copy(); + copy.or(topicBloom); + return this.equals(copy); + } + + public byte[] getData() { + return data; + } + + public Bloom copy() { + return new Bloom(Arrays.copyOf(getData(), getData().length)); + } + + @Override + public String toString() { + return new String(data); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Bloom bloom = (Bloom) o; + + return Arrays.equals(data, bloom.data); + + } + + @Override + public int hashCode() { + return data != null ? Arrays.hashCode(data) : 0; + } +} diff --git a/core/src/main/java/org/platon/core/transaction/ExecuteResult.java b/core/src/main/java/org/platon/core/transaction/ExecuteResult.java new file mode 100644 index 0000000..8964359 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/ExecuteResult.java @@ -0,0 +1,66 @@ +package org.platon.core.transaction; + +import java.math.BigInteger; + +/** transaction execute result + * Created by alliswell on 2018/8/2. + */ +public class ExecuteResult { + private BigInteger energonUsed; + byte[] contractAddress; + byte[] output; + BigInteger energonRefunded; + int depositSize; + BigInteger energonForDeposit; + + public ExecuteResult() { + } + + public void setEnergonUsed(BigInteger energonUsed) { + this.energonUsed = energonUsed; + } + + public void setContractAddress(byte[] contractAddress) { + this.contractAddress = contractAddress; + } + + public void setOutput(byte[] output) { + this.output = output; + } + + public void setEnergonRefunded(BigInteger energonRefunded) { + this.energonRefunded = energonRefunded; + } + + public void setDepositSize(int depositSize) { + this.depositSize = depositSize; + } + + public void setEnergonForDeposit(BigInteger energonForDeposit) { + this.energonForDeposit = energonForDeposit; + } + + public BigInteger getEnergonUsed() { + return energonUsed; + } + + public byte[] getContractAddress() { + return contractAddress; + } + + public byte[] getOutput() { + return output; + } + + public BigInteger getEnergonRefunded() { + return energonRefunded; + } + + public int getDepositSize() { + return depositSize; + } + + public BigInteger getEnergonForDeposit() { + return energonForDeposit; + } +} diff --git a/core/src/main/java/org/platon/core/transaction/LogInfo.java b/core/src/main/java/org/platon/core/transaction/LogInfo.java new file mode 100644 index 0000000..4030749 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/LogInfo.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.transaction; + + +//import com.google.common.primitives.Bytes; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.wrapper.DataWord; +import org.platon.core.transaction.proto.TransactionReceiptProto; +import org.platon.core.transaction.util.HashUtil; + +import java.util.ArrayList; +import java.util.List; + +import static org.platon.core.transaction.util.ByteUtil.toHexString; + +/** + * @author Roman Mandeleil + * @since 19.11.2014 + */ +public class LogInfo { + + byte[] address = new byte[]{}; + //todo need to define the correct type for list, ethereum use DataWord, what we really need? list ok? + List topics = new ArrayList<>(); + byte[] data = new byte[]{}; + + public LogInfo(byte[] protoEncode) { + try { + TransactionReceiptProto.LogEntry logEntry = TransactionReceiptProto.LogEntry.parseFrom(protoEncode); + + if (logEntry.getAddress() != null) { + address = logEntry.getAddress().toByteArray(); + } + + if(logEntry.getTopicCount() > 0) { + for(int i = 0; i < logEntry.getTopicCount(); i++) { + byte[] logTopic = logEntry.getTopic(i).toByteArray(); + topics.add(DataWord.of(logTopic)); + } + } + + if(logEntry.getData() != null) { + data = logEntry.getData().toByteArray(); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode log entry fail!"); + } + } + + public LogInfo(byte[] address, List topics, byte[] data) { + this.address = (address != null) ? address : new byte[]{}; + //todo what data type is needed for receipt log? List is ok? + this.topics = (topics != null) ? topics : new ArrayList<>(); + this.data = (data != null) ? data : new byte[]{}; + } + + public byte[] getAddress() { + return address; + } + + public List getTopics() { + return topics; + } + + public byte[] getData() { + return data; + } + + /* [address, [topic, topic ...] data] */ + public byte[] getEncoded() { + TransactionReceiptProto.LogEntry.Builder logEntry = TransactionReceiptProto.LogEntry.newBuilder(); + + if (address != null && address.length > 0) { + logEntry.setAddress(ByteString.copyFrom(address)); + } + + if (topics != null && topics.size() > 0) { + for (DataWord logTopic : topics) { + logEntry.addTopic(ByteString.copyFrom(logTopic.getData())); + } + } + + if(data != null && data.length > 0) { + logEntry.setData(ByteString.copyFrom(data)); + } + + return logEntry.build().toByteArray(); + } + + public TransactionReceiptProto.LogEntry getEncodedBuilder() { + TransactionReceiptProto.LogEntry.Builder logEntry = TransactionReceiptProto.LogEntry.newBuilder(); + + if (address != null && address.length > 0) { + logEntry.setAddress(ByteString.copyFrom(address)); + } + + if (topics != null && topics.size() > 0) { + for (DataWord logTopic : topics) { + logEntry.addTopic(ByteString.copyFrom(logTopic.getData())); + } + } + + if(data != null && data.length > 0) { + logEntry.setData(ByteString.copyFrom(data)); + } + + return logEntry.build(); + } + + public Bloom getBloom() { + Bloom ret = Bloom.create(HashUtil.bcSHA3Digest256(address)); + for (DataWord topic : topics) { + ret.or(Bloom.create(HashUtil.bcSHA3Digest256(topic.getData()))); + } + return ret; + } + + @Override + public String toString() { + + StringBuilder topicsStr = new StringBuilder(); + topicsStr.append("["); + + for (DataWord topic : topics) { + String topicStr = toHexString(topic.getData()); + topicsStr.append(topicStr).append(" "); + } + topicsStr.append("]"); + + return "LogInfo{" + + "address=" + toHexString(address) + + ", topics=" + topicsStr + + ", data=" + toHexString(data) + + '}'; + } + +// public static final MemSizeEstimator MemEstimator = log -> +// ByteArrayEstimator.estimateSize(log.address) + +// ByteArrayEstimator.estimateSize(log.data) + +// log.topics.size() * DataWord.MEM_SIZE + 16; +} diff --git a/core/src/main/java/org/platon/core/transaction/Transaction.java b/core/src/main/java/org/platon/core/transaction/Transaction.java new file mode 100644 index 0000000..641d115 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/Transaction.java @@ -0,0 +1,722 @@ +package org.platon.core.transaction; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.bouncycastle.util.encoders.Base64; +import org.platon.core.transaction.proto.*; +import org.platon.core.transaction.util.ByteUtil; +import org.platon.crypto.ECKey; +import org.platon.crypto.ECKey.ECDSASignature; +import org.platon.crypto.ECKey.MissingPrivateKeyException; +import org.platon.crypto.HashUtil; +import org.platon.crypto.WalletUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SignatureException; +import java.util.Arrays; + +import static org.apache.commons.lang3.ArrayUtils.isEmpty; + +/** + * A transaction (formally, T) is a single cryptographically + * signed instruction sent by an actor external to platon. + * An external actor can be a person (via a mobile device or desktop computer) + * or could be from a piece of automated software running on a server. + * There are six types of transactions: + * TRANSACTION, // the normal transaction + * VOTE, // vote + * CONTRACT_DEPLOY, // contract deploy + * CONTRACT_CALL, // contract call + * TRANSACTION_MPC, // MPC and other special transaction + * QUERY_CALL, // query call + * PERMISSION_UPDATE // permission update + */ +public class Transaction { + + private static final Logger logger = LoggerFactory.getLogger(Transaction.class); + private static final BigInteger DEFAULT_ENERGON_PRICE = new BigInteger("10000000000000"); + private static final BigInteger DEFAULT_BALANCE_ENERGON = new BigInteger("21000"); + + public static final int HASH_LENGTH = 32; + public static final int ADDRESS_LENGTH = 20; + + /* SHA3 hash of the proto3 encoded transaction */ + private byte[] hash; + + /* specify what transaction type is */ + private TransactionType transactionType; + + /* the amount of energon to transfer (calculated as the base unit) */ + private BigInteger value; + + /* the address of the destination account + * In creation transaction the receive address is - 0 */ + private byte[] receiveAddress; + + private long referenceBlockNum; + + private byte[] referenceBlockHash; + + /* the amount of energon to pay as a transaction fee + * to the miner for each unit of energon */ + private BigInteger energonPrice; + + /* the amount of "energon" to allow for the computation. + * energon is the fuel of the computational engine; + * every computational step taken and every byte added + * to the state or transaction list consumes some energon. */ + private BigInteger energonLimit; + + /* An unlimited size byte array specifying + * input [data] of the message call or + * Initialization code for a new contract */ + private byte[] data; + + /** + * encode chainId in V (or one of RSV) + */ + private static final int CHAIN_ID_INC = 35; + + private static final int LOWER_REAL_V = 27; + + private Integer chainId = null; + + /* the elliptic curve signature + * (including public key recovery bits) */ + private ECDSASignature signature; + + protected byte[] sendAddress; + + /* Tx in encoded form */ + protected byte[] protoEncoded; + + // sha3 hash of the proto buff 3 encoded transaction data without any signature data + private byte[] rawHash; + + /* Indicates if this transaction has been parsed + * from the proto-encoded data */ + protected boolean parsed = false; + + public Transaction(byte[] rawData) { + this.protoEncoded = rawData; + parsed = false; + } + + public Transaction(TransactionType type, BigInteger value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, BigInteger energonPrice, + BigInteger energonLimit, byte[] data, Integer chainId) { + this.transactionType = type; + this.referenceBlockNum = referenceBlockNum; + this.referenceBlockHash = referenceBlockHash; + this.energonPrice = energonPrice; + this.energonLimit = energonLimit; + this.receiveAddress = receiveAddress; + this.value = value; + if(this.referenceBlockNum < 0) { + // throw new RuntimeException()? + this.referenceBlockNum = 0; + } + this.data = data; + this.chainId = chainId; + + if (receiveAddress == null) { + this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY; + } + + parsed = true; + } + + public Transaction(TransactionType type, BigInteger value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, BigInteger energonPrice, + BigInteger energonLimit, byte[] data) { + this(type, value, receiveAddress, referenceBlockNum, referenceBlockHash, energonPrice, energonLimit, data, null); + } + + public Transaction(TransactionType type, BigInteger value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, BigInteger energonPrice, + BigInteger energonLimit, byte[] data, byte[] r, byte[] s, byte v, Integer chainId) { + this(type, value, receiveAddress, referenceBlockNum, referenceBlockHash, energonPrice, energonLimit, data, chainId); + this.signature = ECDSASignature.fromComponents(r, s, v); + } + + public Transaction(TransactionType type, BigInteger value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, BigInteger energonPrice, + BigInteger energonLimit, byte[] data, byte[] r, byte[] s, byte v) { + this(type, value, receiveAddress, referenceBlockNum, referenceBlockHash, energonPrice, energonLimit, data, r, s, v, null); + } + + private Integer extractChainIdFromRawSignature(BigInteger bv, byte[] r, byte[] s) { + if (r == null && s == null) return bv.intValue(); + if (bv.bitLength() > 31) return Integer.MAX_VALUE; + long v = bv.longValue(); + if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) return null; + return (int) ((v - CHAIN_ID_INC) / 2); + } + + private byte getRealV(BigInteger bv) { + if (bv.bitLength() > 31) return 0; + long v = bv.longValue(); + if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) return (byte) v; + byte realV = LOWER_REAL_V; + int inc = 0; + if ((int) v % 2 == 0) inc = 1; + return (byte) (realV + inc); + } + + public synchronized void verify() { + protoParse(); + validate(); + } + + public synchronized void protoParse() { + if (parsed) { + return; + } + try { + org.platon.core.transaction.proto.Transaction tx = org.platon.core.transaction.proto.Transaction.parseFrom(protoEncoded); + + TransactionBody transactionBase = tx.getBody(); + + this.transactionType = transactionBase.getType(); + if(transactionBase.getValue() != null){ + this.value = new BigInteger(transactionBase.getValue().toByteArray()); + } + + if(transactionBase.getReceiveAddress() != null) { + this.receiveAddress = transactionBase.getReceiveAddress().toByteArray(); + } + this.referenceBlockNum = transactionBase.getReferenceBlockNum(); + + if(transactionBase.getReferenceBlockHash() != null) { + this.referenceBlockHash = transactionBase.getReferenceBlockHash().toByteArray(); + } + if (transactionBase.getEnergonPrice() != null) { + this.energonPrice = new BigInteger(transactionBase.getEnergonPrice().toByteArray()); + } + if(transactionBase.getEnergonLimit() != null) { + this.energonLimit = new BigInteger(transactionBase.getEnergonLimit().toByteArray()); + } + if(transactionBase.getData() != null) { + Any any = transactionBase.getData(); + this.data = any.getValue().toByteArray(); + } + if (tx.getSignature() != null) { + byte[] vData = tx.getSignature().toByteArray(); + ECDSASignature signature = WalletUtil.signToECDSASignature(vData); + byte[] r = signature.r.toByteArray(); + byte[] s = signature.s.toByteArray(); + BigInteger v = new BigInteger(String.valueOf(signature.v)); + this.chainId = extractChainIdFromRawSignature(v, r, s); + byte realV = getRealV(BigInteger.valueOf(signature.v)); + signature.v = realV; + this.signature = signature; + } else { + logger.debug("proto buff encoded tx is not signed!"); + } + this.hash = HashUtil.sha3(transactionBase.toByteArray()); + this.parsed = true; + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode transaction exception!"); + } + } + + private void validate() { + if (receiveAddress != null && receiveAddress.length != 0 && receiveAddress.length != ADDRESS_LENGTH) { + throw new RuntimeException("Receive address is not valid"); + } + + if(referenceBlockNum < 0) { + throw new RuntimeException("reference block number is not valid"); + } + + if (referenceBlockHash != null && referenceBlockHash.length > HASH_LENGTH) { + throw new RuntimeException("reference Block Hash is not valid"); + } + + if (energonLimit.compareTo(BigInteger.ZERO) < 0) { + throw new RuntimeException("Energon Limit is not valid"); + } + + if (energonPrice.compareTo(BigInteger.ZERO) < 0) { + throw new RuntimeException("Energon Price is not valid price"); + } + + if (value.compareTo(BigInteger.ZERO) < 0) { + throw new RuntimeException("Value is not valid"); + } + + if (getSignature() != null) { + if (BigIntegers.asUnsignedByteArray(signature.r).length > HASH_LENGTH) + throw new RuntimeException("Signature R is not valid"); + if (BigIntegers.asUnsignedByteArray(signature.s).length > HASH_LENGTH) + throw new RuntimeException("Signature S is not valid"); + if (getSender() != null && getSender().length != ADDRESS_LENGTH) + //todo need to fix the address (20 bytes) + //throw new RuntimeException("Sender is not valid"); + logger.error("Error! Sender Address length is not correct"); + } + } + + public boolean isParsed() { + return parsed; + } + + public byte[] getHash() { + if (!isEmpty(hash)) { + return hash; + } + protoParse(); + getEncoded(); + return hash; + } + + public byte[] getRawHash() { + protoParse(); + if (rawHash != null) { + return rawHash; + } + byte[] plainMsg = this.getEncodedRaw(); + return rawHash = HashUtil.sha3(plainMsg); + } + + public boolean isValueTx() { + protoParse(); + return value != null; + } + + public BigInteger getValue() { + protoParse(); + return value == null ? BigInteger.ZERO : value; + } + + protected void setValue(BigInteger value) { + this.value = value; + parsed = true; + } + + public byte[] getReceiveAddress() { + protoParse(); + return receiveAddress; + } + + protected void setReceiveAddress(byte[] receiveAddress) { + this.receiveAddress = receiveAddress; + parsed = true; + } + + public BigInteger getEnergonPrice() { + protoParse(); + return energonPrice == null ? BigInteger.ZERO : energonPrice; + } + + protected void setEnergonPrice(BigInteger energonPrice) { + this.energonPrice = energonPrice; + parsed = true; + } + + public BigInteger getEnergonLimit() { + protoParse(); + return energonLimit == null ? BigInteger.ZERO : energonLimit; + } + + protected void setEnergonLimit(BigInteger energonLimit) { + this.energonLimit = energonLimit; + parsed = true; + } + + public long nonZeroDataBytes() { + if (data == null) return 0; + int counter = 0; + for (final byte aData : data) { + if (aData != 0) ++counter; + } + return counter; + } + + public long zeroDataBytes() { + if (data == null) return 0; + int counter = 0; + for (final byte aData : data) { + if (aData == 0) ++counter; + } + return counter; + } + + + public byte[] getData() { + protoParse(); + return data; + } + + protected void setData(byte[] data) { + this.data = data; + parsed = true; + } + + public ECDSASignature getSignature() { + protoParse(); + return signature; + } + + public long getReferenceBlockNum() { + protoParse(); + return referenceBlockNum; + } + + protected void setReferenceBlockNum(long referenceBlockNum) { + this.referenceBlockNum = referenceBlockNum; + parsed = true; + } + + public byte[] getReferenceBlockHash() { + protoParse(); + return referenceBlockHash; + } + + protected void setReferenceBlockHash(byte[] referenceBlockHash){ + this.referenceBlockHash = referenceBlockHash; + parsed = true; + } + + public TransactionType getTransactionType() { + protoParse(); + return transactionType; + } + + protected void setTransactionType(TransactionType type) { + transactionType = type; + parsed = true; + } + + public byte[] getContractAddress() { + if (!isContractCreation()) { + return null; + } + + if(this.getSender() == null || this.getHash() == null) { + return null; + } + + byte[] addrParameter = new byte[Transaction.HASH_LENGTH + Transaction.ADDRESS_LENGTH]; + System.arraycopy(this.getSender(), 0, addrParameter, 0, Transaction.ADDRESS_LENGTH); + System.arraycopy(this.getHash(), 0, addrParameter, Transaction.ADDRESS_LENGTH, Transaction.HASH_LENGTH); + byte[] addrHash = HashUtil.sha3(addrParameter); + return Arrays.copyOfRange(addrHash, 12, addrHash.length); + } + + public boolean isContractCreation() { + protoParse(); + return this.receiveAddress == null || Arrays.equals(this.receiveAddress,ByteUtil.EMPTY_BYTE_ARRAY); + } + + //todo it seems no need to get ECKey from signature, maybe implement it in ECkey.java if it is needed +// public ECKey getKey() { +// byte[] hash = getRawHash(); +// return ECKey.recoverFromSignature(signature.v, signature, hash); +// } + +// public static ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash) { +// final byte[] pubBytes = ECKey.recoverPubBytesFromSignature(recId, sig, messageHash); +// if (pubBytes == null) { +// return null; +// } else { +// //return ECKey.fromPublicOnly(pubBytes); +// return new ECKey(null, CURVE.getCurve().decodePoint(pubBytes)); +// } +// } + + public synchronized byte[] getSender() { + try { + if (sendAddress == null && getSignature() != null) { + sendAddress = WalletUtil.signatureToAddress(getHash(), WalletUtil.signToByteArray(getSignature())); + } + return sendAddress; + } catch (SignatureException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + public Integer getChainId() { + protoParse(); + return chainId == null ? null : (int) chainId; + } + + public void sign(ECKey key) throws MissingPrivateKeyException { + this.signature = WalletUtil.signature(this.getRawHash(), key); + this.protoEncoded = null; + } + + @Override + public String toString() { + return "Transaction{" + + "hash=" + Arrays.toString(hash) + + ", transactionType=" + transactionType + + ", value=" + value + + ", receiveAddress=" + Arrays.toString(receiveAddress) + + ", referenceBlockNum=" + referenceBlockNum + + ", referenceBlockHash=" + Arrays.toString(referenceBlockHash) + + ", energonPrice=" + energonPrice + + ", energonLimit=" + energonLimit + + ", data=" + Arrays.toString(data) + + ", chainId=" + chainId + + ", signature=" + signature + + ", sendAddress=" + Arrays.toString(sendAddress) + + ", protoEncoded=" + Arrays.toString(protoEncoded) + + ", rawHash=" + Arrays.toString(rawHash) + + ", parsed=" + parsed + + '}'; + } + + /** + * For signatures you have to keep also + * proto buff encode of the transaction without any signature data + */ + public byte[] getEncodedRaw() { + protoParse(); + + byte[] protoRaw; + + TransactionBody.Builder transactionBase = TransactionBody.newBuilder(); + + transactionBase.setType(transactionType); + transactionBase.setValue(ByteString.copyFrom(value.toByteArray())); + transactionBase.setReceiveAddress(ByteString.copyFrom(receiveAddress)); + transactionBase.setReferenceBlockNum(referenceBlockNum); + transactionBase.setReferenceBlockHash(ByteString.copyFrom(referenceBlockHash)); + transactionBase.setEnergonLimit(ByteString.copyFrom(energonLimit.toByteArray())); + transactionBase.setEnergonPrice(ByteString.copyFrom(energonPrice.toByteArray())); + + if(data != null && data.length != 0) { + switch (transactionType) { + case TRANSACTION_MPC: + try { + MPCTransactionRequest mpcTransactionRequest = MPCTransactionRequest.parseFrom(data); + Any any = Any.pack(mpcTransactionRequest); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto.buff decode MPC transaction request fail!"); + } + break; + + case CONTRACT_DEPLOY: + try { + ContractDeployRequest contractDeployReq = ContractDeployRequest.parseFrom(data); + Any any = Any.pack(contractDeployReq); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto.buff decode contract deploy request fail!"); + } + break; + + case CONTRACT_CALL: + try { + ContractRequest contractCallRequest = ContractRequest.parseFrom(data); + Any any = Any.pack(contractCallRequest); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto.buff decode contract call request fail!"); + } + break; + + default: + try { + defaultRequestData defaultRequestData = org.platon.core.transaction.proto.defaultRequestData.parseFrom(data); + Any any = Any.pack(defaultRequestData); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto.buff decode default Data request fail!"); + } + } + } + +// if(chainId != null) { +// ECDSASignature newSignature = new ECDSASignature(new BigInteger(ByteUtil.EMPTY_BYTE_ARRAY), +// new BigInteger(ByteUtil.EMPTY_BYTE_ARRAY), chainId.byteValue()); +// transactionBase.setSignature(ByteString.copyFrom(newSignature.toByteArray())); +// } + + protoRaw = transactionBase.build().toByteArray(); + + return protoRaw; + } + + public byte[] getEncoded() { + + if (protoEncoded != null) { + return protoEncoded; + } + org.platon.core.transaction.proto.Transaction.Builder txBuilder = org.platon.core.transaction.proto.Transaction.newBuilder(); + TransactionBody.Builder transactionBase = TransactionBody.newBuilder(); + + transactionBase.setType(transactionType); + transactionBase.setValue(ByteString.copyFrom(value.toByteArray())); + transactionBase.setReceiveAddress(ByteString.copyFrom(receiveAddress)); + transactionBase.setReferenceBlockNum(referenceBlockNum); + transactionBase.setReferenceBlockHash(ByteString.copyFrom(referenceBlockHash)); + transactionBase.setEnergonLimit(ByteString.copyFrom(energonLimit.toByteArray())); + transactionBase.setEnergonPrice(ByteString.copyFrom(energonPrice.toByteArray())); + + if(data != null && data.length != 0) { + switch (transactionType) { + case CONTRACT_DEPLOY: + try { + ContractDeployRequest contractDeployRequest = ContractDeployRequest.parseFrom(data); + Any any = Any.pack(contractDeployRequest); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode contract deploy request fail!"); + } + break; + + case CONTRACT_CALL: + try { + ContractRequest contractCallRequest = ContractRequest.parseFrom(data); + Any any = Any.pack(contractCallRequest); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode contract call request fail!"); + } + break; + + case TRANSACTION_MPC: + try { + MPCTransactionRequest mpcTransactionRequest = MPCTransactionRequest.parseFrom(data); + Any any = Any.pack(mpcTransactionRequest); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode MPC transaction request fail!"); + } + break; + + default: + try { + defaultRequestData defaultRequestData = org.platon.core.transaction.proto.defaultRequestData.parseFrom(data); + Any any = Any.pack(defaultRequestData); + transactionBase.setData(any); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode default Data request fail!"); + } + } + } + + //todo test and verify this EMPTY_BYTE_ARRAY, and also check if this is really needed? + if (signature == null) { + // todo if the signature is null, no need to encode an empty signature +// byte v = chainId == null ? (ByteUtil.EMPTY_BYTE_ARRAY)[0] : chainId.byteValue(); +// ECDSASignature newSignature = new ECDSASignature(new BigInteger(ByteUtil.EMPTY_BYTE_ARRAY), +// new BigInteger(ByteUtil.EMPTY_BYTE_ARRAY), v); +// transactionBase.setSignature(ByteString.copyFrom(newSignature.toByteArray())); + } else { +// int encodeV; +// if(chainId == null) { +// encodeV = signature.v;getEncodedRaw +// } else { +// encodeV = signature.v - LOWER_REAL_V; +// encodeV += chainId * 2 + CHAIN_ID_INC; +// } + txBuilder.setSignature(ByteString.copyFrom(Base64.encode(signature.toByteArray()))); + } + + txBuilder.setBody(transactionBase.build()); + protoEncoded = txBuilder.build().toByteArray(); + + this.hash = HashUtil.sha3(protoEncoded); + return protoEncoded; + } + + @Override + //todo what the purpose of this function? + public int hashCode() { + + byte[] hash = this.getHash(); + int hashCode = 0; + + for (int i = 0; i < hash.length; ++i) { + hashCode += hash[i] * i; + } + + return hashCode; + } + + @Override + public boolean equals(Object obj) { + + if (!(obj instanceof Transaction)) return false; + Transaction tx = (Transaction) obj; + + return tx.hashCode() == this.hashCode(); + } + + /** + * @deprecated Use {@link Transaction#createDefault(TransactionType, BigInteger, byte[], long, BigInteger, Integer)} instead + */ + public static Transaction createDefault(TransactionType type, BigInteger amount, + byte[] to, long referenceBlockNum, BigInteger referenceBlockHash){ + return create(type, to, amount, referenceBlockNum, referenceBlockHash, DEFAULT_ENERGON_PRICE, DEFAULT_BALANCE_ENERGON); + } + + public static Transaction createDefault(TransactionType type, BigInteger amount, + byte[] to, long referenceBlockNum, BigInteger referenceBlockHash, Integer chainId){ + return create(type, to, amount, referenceBlockNum, referenceBlockHash, DEFAULT_ENERGON_PRICE, DEFAULT_BALANCE_ENERGON, chainId); + } + + /** + * @deprecated use {@link Transaction#create(TransactionType, byte[], + * BigInteger, long, BigInteger, + * BigInteger, BigInteger, Integer)} instead + */ + public static Transaction create(TransactionType type, byte[] to, + BigInteger amount, long referenceBlockNum, BigInteger referenceBlockHash, + BigInteger energonPrice, BigInteger energonLimit){ + return new Transaction(type, + amount, + to, + referenceBlockNum, + BigIntegers.asUnsignedByteArray(referenceBlockHash), + energonPrice, + energonLimit, + null); + } + + public static Transaction create(TransactionType type, byte[] to, + BigInteger amount, long referenceBlockNum, BigInteger referenceBlockHash, + BigInteger energonPrice, BigInteger energonLimit, Integer chainId){ + return new Transaction(type, + amount, + to, + referenceBlockNum, + BigIntegers.asUnsignedByteArray(referenceBlockHash), + energonPrice, + energonLimit, + null, + chainId); + } + +// public static final MemSizeEstimator MemEstimator = tx -> +// ByteArrayEstimator.estimateSize(tx.hash) + +// ByteArrayEstimator.estimateSize(tx.nonce) + +// ByteArrayEstimator.estimateSize(tx.value) + +// ByteArrayEstimator.estimateSize(tx.energonPrice) + +// ByteArrayEstimator.estimateSize(tx.energonLimit) + +// ByteArrayEstimator.estimateSize(tx.data) + +// ByteArrayEstimator.estimateSize(tx.sendAddress) + +// ByteArrayEstimator.estimateSize(tx.rlpEncoded) + +// ByteArrayEstimator.estimateSize(tx.rawHash) + +// (tx.chainId != null ? 24 : 0) + +// (tx.signature != null ? 208 : 0) + // approximate size of signature +// 16; // Object header + ref +} diff --git a/core/src/main/java/org/platon/core/transaction/TransactionComparator.java b/core/src/main/java/org/platon/core/transaction/TransactionComparator.java new file mode 100644 index 0000000..c1a1658 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/TransactionComparator.java @@ -0,0 +1,11 @@ +package org.platon.core.transaction; + +/** + * Created by alliswell on 2018/7/24. + */ +public class TransactionComparator implements java.util.Comparator { + @Override + public int compare(Transaction t1, Transaction t2) { + return t1.getEnergonPrice().subtract(t2.getEnergonPrice()).intValue(); + } +} diff --git a/core/src/main/java/org/platon/core/transaction/TransactionPool.java b/core/src/main/java/org/platon/core/transaction/TransactionPool.java new file mode 100644 index 0000000..854c5e5 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/TransactionPool.java @@ -0,0 +1,143 @@ +package org.platon.core.transaction; + +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.utils.ByteUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.PriorityBlockingQueue; + +/** + * transaction poo class + * Created by alliswell on 2018/7/23. + */ + +public class TransactionPool { + + private static final Logger logger = LoggerFactory.getLogger(TransactionPool.class); + private static volatile TransactionPool instance; + /* priority queue of the valid transaction */ + private PriorityBlockingQueue queue; + /* priority queue of the valid transaction */ + private HashSet known; + + private TransactionPool(int initialCapacity) { + queue = new PriorityBlockingQueue(initialCapacity, new TransactionComparator()); + known = new HashSet<>(initialCapacity); + } + + public static TransactionPool getInstance() { + if (instance == null) { + synchronized (TransactionPool.class) { + if (instance == null) { + instance = new TransactionPool(2048); + } + } + } + return instance; + } + + /** + * inject a transaction to the Q + * + * @param tx: Transaction to be injected + * @return 0:OK, other: failed + */ + public synchronized int inject(Transaction tx) { + int ret = checkTx(tx); + if (0 == ret) { + queue.put(tx); + known.add(tx.getHash()); + } + return ret; + } + + /** + * checking if we have known the hash + * + * @param hash : hash of the tx + * @return if we have already received this tx return true + */ + public boolean isKnown(byte[] hash) { + return ByteUtil.contains(known, hash); + } + + /** + * return the number of tx in queue + * + * @return int + */ + public int pending() { + return queue.size(); + } + + /** + * clear the pool + */ + public void clear() { + queue.clear(); + known.clear(); + } + + /** + * drop the tx from the Q + * + * @param tx + */ + public synchronized void drop(Transaction tx) { + queue.remove(tx); + known.remove(tx.getHash()); + } + + /** + * consume the Q + * + * @param limit : max size + * @param avoid : a set of avoid hash + * @return ArrayList of Transaction + */ + public synchronized ArrayList consume(int limit, HashSet avoid) { + ArrayList ret = new ArrayList(); + int total = 0; + while (!queue.isEmpty()) { + Transaction tx = queue.poll(); + if (tx != null && !ByteUtil.contains(avoid, tx.getHash())) { + ret.add(tx); + ++total; + } + if (total >= limit) { + break; + } + } + return ret; + } + + /** + * check if a Transaction is valid + * + * @param tx: Transaction to be checked + * @return 0:OK, other: failed + */ + private int checkTx(Transaction tx) { + if (tx.getEnergonPrice().intValue() > 0) { + return 0; + } + if (queue.contains(tx)) { + logger.debug("tx[" + Hex.toHexString(tx.getHash()) + "] has already in pool!"); + return 1; + } + + //TODO: theck the tx's hash in TransactionHashPool + return -1; + } +} + + + + + + + + diff --git a/core/src/main/java/org/platon/core/transaction/TransactionReceipt.java b/core/src/main/java/org/platon/core/transaction/TransactionReceipt.java new file mode 100644 index 0000000..b732fe0 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/TransactionReceipt.java @@ -0,0 +1,264 @@ +package org.platon.core.transaction; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.core.transaction.proto.TransactionReceiptProto; +import org.platon.core.transaction.util.ByteUtil; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.platon.core.transaction.util.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.platon.core.transaction.util.ByteUtil.toHexString; + +/** + * The transaction receipt is a tuple of three items + * comprising the transaction, together with the post-transaction state, + * and the cumulative energon used in the block containing the transaction receipt + * as of immediately after the transaction has happened, + */ +public class TransactionReceipt { + + private Transaction transaction; + + private byte[] stateRoot = EMPTY_BYTE_ARRAY; + private byte[] cumulativeEnergon = EMPTY_BYTE_ARRAY; + private Bloom bloomFilter = new Bloom(); + private List logInfoList = new ArrayList<>(); + + private byte[] energonUsed = EMPTY_BYTE_ARRAY; + private byte[] executionResult = EMPTY_BYTE_ARRAY; + private String error = ""; + + /* Tx Receipt in encoded form */ + private byte[] protoEncoded; + + public TransactionReceipt() { + } + + public TransactionReceipt(byte[] protoTransactionReceipt) { + + try { + TransactionReceiptProto.TransactionReceiptBase transactionReceiptBase + = TransactionReceiptProto.TransactionReceiptBase.parseFrom(protoTransactionReceipt); + + this.energonUsed = transactionReceiptBase.getEnergonUsed().toByteArray(); + this.cumulativeEnergon = transactionReceiptBase.getCumulativeEnergon().toByteArray(); + this.stateRoot = transactionReceiptBase.getStateRoot().toByteArray(); + this.bloomFilter = new Bloom(transactionReceiptBase.getBloomFilter().toByteArray()); + this.executionResult = transactionReceiptBase.getExecutionResult().toByteArray(); + + for (int i = 0; i < transactionReceiptBase.getLogsCount(); i++) { + byte[] logs = transactionReceiptBase.getLogs(i).toByteArray(); + LogInfo logInfo = new LogInfo(logs); + logInfoList.add(logInfo); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Proto buff decode transactionReceipt exception!"); + } + + protoEncoded = protoTransactionReceipt; + } + + + public TransactionReceipt(byte[] stateRoot, byte[] cumulativeEnergon, + Bloom bloomFilter, List logInfoList) { + this.stateRoot = stateRoot; + this.cumulativeEnergon = cumulativeEnergon; + this.bloomFilter = bloomFilter; + this.logInfoList = logInfoList; + } + + public byte[] getStateRoot() { + return stateRoot; + } + + public byte[] getCumulativeEnergon() { + return cumulativeEnergon; + } + + public byte[] getEnergonUsed() { + return energonUsed; + } + + public byte[] getExecutionResult() { + return executionResult; + } + + public long getCumulativeEnergonLong() { + return new BigInteger(1, cumulativeEnergon).longValue(); + } + + + public Bloom getBloomFilter() { + return bloomFilter; + } + + public void setBloomFilter(Bloom bloomFilter) { + this.bloomFilter = bloomFilter; + } + + public List getLogInfoList() { + return logInfoList; + } + + public boolean isValid() { + return ByteUtil.byteArrayToLong(energonUsed) > 0; + } + + public boolean isSuccessful() { + return error.isEmpty(); + } + + public String getError() { + return error; + } + + /** + * Used for Receipt trie hash calculation. Should contain only the following items encoded: + * [postTxState, cumulativeGas, bloomFilter, logInfoList] + */ + public byte[] getReceiptTrieEncoded() { + return getEncoded(true); + } + + /** + * Used for serialization, contains all the receipt data encoded + */ + public byte[] getEncoded() { + if (protoEncoded == null) { + protoEncoded = getEncoded(false); + } + + return protoEncoded; + } + + public byte[] getEncoded(boolean receiptTrie) { + TransactionReceiptProto.TransactionReceiptBase.Builder transactionReceipt = + TransactionReceiptProto.TransactionReceiptBase.newBuilder(); + + transactionReceipt.setCumulativeEnergon(ByteString.copyFrom(cumulativeEnergon)); + transactionReceipt.setStateRoot(ByteString.copyFrom(stateRoot)); + transactionReceipt.setBloomFilter(ByteString.copyFrom(bloomFilter.getData())); + + if(logInfoList != null && logInfoList.size() > 0) { + for (int i = 0; i < logInfoList.size(); i++) { + transactionReceipt.addLogs(logInfoList.get(i).getEncodedBuilder()); + } + } + + //todo fix me here, is it correct? + if(receiptTrie) { + transactionReceipt.setEnergonUsed(ByteString.copyFrom(energonUsed)); + transactionReceipt.setExecutionResult(ByteString.copyFrom(executionResult)); + } + + return transactionReceipt.build().toByteArray(); + } + + public void setStateRoot(byte[] stateRoot) { + this.stateRoot = stateRoot; + protoEncoded = null; + } + + public void setTxStatus(boolean success) { + this.stateRoot = success ? new byte[]{1} : new byte[0]; + protoEncoded = null; + } + + public boolean hasTxStatus() { + return stateRoot != null && stateRoot.length <= 1; + } + + public boolean isTxStatusOK() { + return stateRoot != null && stateRoot.length == 1 && stateRoot[0] == 1; + } + + public void setCumulativeEnergon(long cumulativeEnergon) { + this.cumulativeEnergon = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(cumulativeEnergon)); + protoEncoded = null; + } + + public void setCumulativeEnergon(byte[] cumulativeEnergon) { + this.cumulativeEnergon = cumulativeEnergon; + protoEncoded = null; + } + + public void setEnergonUsed(byte[] energonUsed) { + this.energonUsed = energonUsed; + protoEncoded = null; + } + + public void setEnergonUsed(long energonUsed) { + this.energonUsed = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(energonUsed)); + protoEncoded = null; + } + + public void setExecutionResult(byte[] executionResult) { + this.executionResult = executionResult; + protoEncoded = null; + } + + public void setError(String error) { + this.error = error == null ? "" : error; + } + + public void setLogInfoList(List logInfoList) { + if (logInfoList == null) return; + this.logInfoList = logInfoList; + + for (LogInfo loginfo : logInfoList) { + bloomFilter.or(loginfo.getBloom()); + } + protoEncoded = null; + } + + public void setTransaction(Transaction transaction) { + this.transaction = transaction; + } + + public Transaction getTransaction() { + if (transaction == null) throw new NullPointerException("Transaction is not initialized. Use TransactionInfo and BlockStore to setup Transaction instance"); + return transaction; + } + + @Override + public String toString() { + + // todo: fix that + + return "TransactionReceipt[" + + "\n , " + (hasTxStatus() ? ("txStatus=" + (isTxStatusOK() ? "OK" : "FAILED")) + : ("postTxState=" + toHexString(stateRoot))) + + "\n , cumulativeEnergon=" + toHexString(cumulativeEnergon) + + "\n , energonUsed=" + toHexString(energonUsed) + + "\n , error=" + error + + "\n , executionResult=" + toHexString(executionResult) + + "\n , bloom=" + bloomFilter.toString() + + "\n , logs=" + logInfoList + + ']'; + } + +// public long estimateMemSize() { +// return MemEstimator.estimateSize(this); +// } + +// public static final MemSizeEstimator MemEstimator = receipt -> { +// if (receipt == null) { +// return 0; +// } +// long logSize = receipt.logInfoList.stream().mapToLong(LogInfo.MemEstimator::estimateSize).sum() + 16; +// return (receipt.transaction == null ? 0 : Transaction.MemEstimator.estimateSize(receipt.transaction)) + +// (receipt.postTxState == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.postTxState)) + +// (receipt.cumulativeGas == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.cumulativeGas)) + +// (receipt.gasUsed == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.gasUsed)) + +// (receipt.executionResult == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.executionResult)) + +// ByteArrayEstimator.estimateSize(receipt.rlpEncoded) + +// Bloom.MEM_SIZE + +// receipt.error.getBytes().length + 40 + +// logSize; +// }; +} diff --git a/core/src/main/java/org/platon/core/transaction/util/ByteUtil.java b/core/src/main/java/org/platon/core/transaction/util/ByteUtil.java new file mode 100644 index 0000000..5dc39cb --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/util/ByteUtil.java @@ -0,0 +1,729 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.transaction.util; + +import org.platon.common.utils.*; + +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.math.BigInteger; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class ByteUtil { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final byte[] ZERO_BYTE_ARRAY = new byte[]{0}; + + /** + * Creates a copy of bytes and appends b to the end of it + */ + public static byte[] appendByte(byte[] bytes, byte b) { + byte[] result = Arrays.copyOf(bytes, bytes.length + 1); + result[result.length - 1] = b; + return result; + } + + /** + * The regular {@link java.math.BigInteger#toByteArray()} method isn't quite what we often need: + * it appends a leading zero to indicate that the number is positive and may need padding. + * + * @param b the integer to format into a byte array + * @param numBytes the desired size of the resulting byte array + * @return numBytes byte long array. + */ + public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) { + if (b == null) + return null; + byte[] bytes = new byte[numBytes]; + byte[] biBytes = b.toByteArray(); + int start = (biBytes.length == numBytes + 1) ? 1 : 0; + int length = Math.min(biBytes.length, numBytes); + System.arraycopy(biBytes, start, bytes, numBytes - length, length); + return bytes; + } + + public static byte[] bigIntegerToBytesSigned(BigInteger b, int numBytes) { + if (b == null) + return null; + byte[] bytes = new byte[numBytes]; + Arrays.fill(bytes, b.signum() < 0 ? (byte) 0xFF : 0x00); + byte[] biBytes = b.toByteArray(); + int start = (biBytes.length == numBytes + 1) ? 1 : 0; + int length = Math.min(biBytes.length, numBytes); + System.arraycopy(biBytes, start, bytes, numBytes - length, length); + return bytes; + } + + /** + * Omitting sign indication byte. + *

+ * Instead of {@link org.bouncycastle.util.BigIntegers#asUnsignedByteArray(BigInteger)} + *
we use this custom method to avoid an empty array in case of BigInteger.ZERO + * + * @param value - any big integer number. A null-value will return null + * @return A byte array without a leading zero byte if present in the signed encoding. + * BigInteger.ZERO will return an array with length 1 and byte-value 0. + */ + public static byte[] bigIntegerToBytes(BigInteger value) { + if (value == null) + return null; + + byte[] data = value.toByteArray(); + + if (data.length != 1 && data[0] == 0) { + byte[] tmp = new byte[data.length - 1]; + System.arraycopy(data, 1, tmp, 0, tmp.length); + data = tmp; + } + return data; + } + + /** + * Cast hex encoded value from byte[] to BigInteger + * null is parsed like byte[0] + * + * @param bb byte array contains the values + * @return unsigned positive BigInteger value. + */ + public static BigInteger bytesToBigInteger(byte[] bb) { + return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb); + } + + /** + * Returns the amount of nibbles that match each other from 0 ... + * amount will never be larger than smallest input + * + * @param a - first input + * @param b - second input + * @return Number of bytes that match + */ + public static int matchingNibbleLength(byte[] a, byte[] b) { + int i = 0; + int length = a.length < b.length ? a.length : b.length; + while (i < length) { + if (a[i] != b[i]) + return i; + i++; + } + return i; + } + + /** + * Converts a long value into a byte array. + * + * @param val - long value to convert + * @return byte[] of length 8, representing the long value + */ + public static byte[] longToBytes(long val) { + return ByteBuffer.allocate(Long.BYTES).putLong(val).array(); + } + + /** + * Converts a long value into a byte array. + * + * @param val - long value to convert + * @return decimal value with leading byte that are zeroes striped + */ + public static byte[] longToBytesNoLeadZeroes(long val) { + + // todo: improve performance by while strip numbers until (long >> 8 == 0) + if (val == 0) return EMPTY_BYTE_ARRAY; + + byte[] data = ByteBuffer.allocate(Long.BYTES).putLong(val).array(); + + return stripLeadingZeroes(data); + } + + /** + * Converts int value into a byte array. + * + * @param val - int value to convert + * @return byte[] of length 4, representing the int value + */ + public static byte[] intToBytes(int val){ + return ByteBuffer.allocate(Integer.BYTES).putInt(val).array(); + } + + /** + * Converts a int value into a byte array. + * + * @param val - int value to convert + * @return value with leading byte that are zeroes striped + */ + public static byte[] intToBytesNoLeadZeroes(int val){ + + if (val == 0) return EMPTY_BYTE_ARRAY; + + int lenght = 0; + + int tmpVal = val; + while (tmpVal != 0){ + tmpVal = tmpVal >>> 8; + ++lenght; + } + + byte[] result = new byte[lenght]; + + int index = result.length - 1; + while(val != 0){ + + result[index] = (byte)(val & 0xFF); + val = val >>> 8; + index -= 1; + } + + return result; + } + + + /** + * Convert a byte-array into a hex String.
+ * Works similar to {@link Hex#toHexString} + * but allows for null + * + * @param data - byte-array to convert to a hex-string + * @return hex representation of the data.
+ * Returns an empty String if the input is null + * + * @see Hex#toHexString + */ + public static String toHexString(byte[] data) { + return data == null ? "" : Hex.toHexString(data); + } + + /** + * Calculate packet length + * + * @param msg byte[] + * @return byte-array with 4 elements + */ + public static byte[] calcPacketLength(byte[] msg) { + int msgLen = msg.length; + return new byte[]{ + (byte) ((msgLen >> 24) & 0xFF), + (byte) ((msgLen >> 16) & 0xFF), + (byte) ((msgLen >> 8) & 0xFF), + (byte) ((msgLen) & 0xFF)}; + } + + /** + * Cast hex encoded value from byte[] to int + * null is parsed like byte[0] + * + * Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) + * + * @param b array contains the values + * @return unsigned positive int value. + */ + public static int byteArrayToInt(byte[] b) { + if (b == null || b.length == 0) + return 0; + return new BigInteger(1, b).intValue(); + } + + /** + * Cast hex encoded value from byte[] to long + * null is parsed like byte[0] + * + * Limited to Long.MAX_VALUE: 263-1 (8 bytes) + * + * @param b array contains the values + * @return unsigned positive long value. + */ + public static long byteArrayToLong(byte[] b) { + if (b == null || b.length == 0) + return 0; + return new BigInteger(1, b).longValue(); + } + + + /** + * Turn nibbles to a pretty looking output string + * + * Example. [ 1, 2, 3, 4, 5 ] becomes '\x11\x23\x45' + * + * @return pretty string of nibbles + */ + /*public static String nibblesToPrettyString(byte[] nibbles) { + StringBuilder builder = new StringBuilder(); + for (byte nibble : nibbles) { + final String nibbleString = oneByteToHexString(nibble); + builder.append("\\x").append(nibbleString); + } + return builder.toString(); + }*/ + + public static String oneByteToHexString(byte value) { + String retVal = Integer.toString(value & 0xFF, 16); + if (retVal.length() == 1) retVal = "0" + retVal; + return retVal; + } + + /** + * Calculate the number of bytes need + * to encode the number + * + * @param val - number + * @return number of min bytes used to encode the number + */ + public static int numBytes(String val) { + + BigInteger bInt = new BigInteger(val); + int bytes = 0; + + while (!bInt.equals(BigInteger.ZERO)) { + bInt = bInt.shiftRight(8); + ++bytes; + } + if (bytes == 0) ++bytes; + return bytes; + } + + /** + * @param arg - not more that 32 bits + * @return - bytes of the value pad with complete to 32 zeroes + */ + public static byte[] encodeValFor32Bits(Object arg) { + + byte[] data; + + // check if the string is numeric + if (arg.toString().trim().matches("-?\\d+(\\.\\d+)?")) + data = new BigInteger(arg.toString().trim()).toByteArray(); + // check if it's hex number + else if (arg.toString().trim().matches("0[xX][0-9a-fA-F]+")) + data = new BigInteger(arg.toString().trim().substring(2), 16).toByteArray(); + else + data = arg.toString().trim().getBytes(); + + + if (data.length > 32) + throw new RuntimeException("values can't be more than 32 byte"); + + byte[] val = new byte[32]; + + int j = 0; + for (int i = data.length; i > 0; --i) { + val[31 - j] = data[i - 1]; + ++j; + } + return val; + } + + /** + * encode the values and concatenate together + * + * @param args Object + * @return byte[] + */ + public static byte[] encodeDataList(Object... args) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (Object arg : args) { + byte[] val = encodeValFor32Bits(arg); + try { + baos.write(val); + } catch (IOException e) { + throw new Error("Happen something that should never happen ", e); + } + } + return baos.toByteArray(); + } + + public static int firstNonZeroByte(byte[] data) { + for (int i = 0; i < data.length; ++i) { + if (data[i] != 0) { + return i; + } + } + return -1; + } + + public static byte[] stripLeadingZeroes(byte[] data) { + + if (data == null) + return null; + + final int firstNonZero = firstNonZeroByte(data); + switch (firstNonZero) { + case -1: + return ZERO_BYTE_ARRAY; + + case 0: + return data; + + default: + byte[] result = new byte[data.length - firstNonZero]; + System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero); + + return result; + } + } + + /** + * increment byte array as a number until max is reached + * + * @param bytes byte[] + * @return boolean + */ + public static boolean increment(byte[] bytes) { + final int startIndex = 0; + int i; + for (i = bytes.length - 1; i >= startIndex; i--) { + bytes[i]++; + if (bytes[i] != 0) + break; + } + // we return false when all bytes are 0 again + return (i >= startIndex || bytes[startIndex] != 0); + } + + /** + * Utility function to copy a byte array into a new byte array with given size. + * If the src length is smaller than the given size, the result will be left-padded + * with zeros. + * + * @param value - a BigInteger with a maximum value of 2^256-1 + * @return Byte array of given size with a copy of the src + */ + public static byte[] copyToArray(BigInteger value) { + byte[] src = ByteUtil.bigIntegerToBytes(value); + byte[] dest = ByteBuffer.allocate(32).array(); + System.arraycopy(src, 0, dest, dest.length - src.length, src.length); + return dest; + } + + + //todo add ByteArrayWrapper + public static ByteArrayWrapper wrap(byte[] data) { + return new ByteArrayWrapper(data); + } + + public static byte[] setBit(byte[] data, int pos, int val) { + + if ((data.length * 8) - 1 < pos) + throw new Error("outside byte array limit, pos: " + pos); + + int posByte = data.length - 1 - (pos) / 8; + int posBit = (pos) % 8; + byte setter = (byte) (1 << (posBit)); + byte toBeSet = data[posByte]; + byte result; + if (val == 1) + result = (byte) (toBeSet | setter); + else + result = (byte) (toBeSet & ~setter); + + data[posByte] = result; + return data; + } + + public static int getBit(byte[] data, int pos) { + + if ((data.length * 8) - 1 < pos) + throw new Error("outside byte array limit, pos: " + pos); + + int posByte = data.length - 1 - pos / 8; + int posBit = pos % 8; + byte dataByte = data[posByte]; + return Math.min(1, (dataByte & (1 << (posBit)))); + } + + public static byte[] and(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] & b2[i]); + } + return ret; + } + + public static byte[] or(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] | b2[i]); + } + return ret; + } + + public static byte[] xor(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] ^ b2[i]); + } + return ret; + } + + /** + * XORs byte arrays of different lengths by aligning length of the shortest via adding zeros at beginning + */ + public static byte[] xorAlignRight(byte[] b1, byte[] b2) { + if (b1.length > b2.length) { + byte[] b2_ = new byte[b1.length]; + System.arraycopy(b2, 0, b2_, b1.length - b2.length, b2.length); + b2 = b2_; + } else if (b2.length > b1.length) { + byte[] b1_ = new byte[b2.length]; + System.arraycopy(b1, 0, b1_, b2.length - b1.length, b1.length); + b1 = b1_; + } + + return xor(b1, b2); + } + + /** + * @param arrays - arrays to merge + * @return - merged array + */ + public static byte[] merge(byte[]... arrays) + { + int count = 0; + for (byte[] array: arrays) + { + count += array.length; + } + + // Create new array and copy all array contents + byte[] mergedArray = new byte[count]; + int start = 0; + for (byte[] array: arrays) { + System.arraycopy(array, 0, mergedArray, start, array.length); + start += array.length; + } + return mergedArray; + } + + public static boolean isNullOrZeroArray(byte[] array){ + return (array == null) || (array.length == 0); + } + + public static boolean isSingleZero(byte[] array){ + return (array.length == 1 && array[0] == 0); + } + + + public static Set difference(Set setA, Set setB){ + + Set result = new HashSet<>(); + + for (byte[] elementA : setA){ + boolean found = false; + for (byte[] elementB : setB){ + + if (Arrays.equals(elementA, elementB)){ + found = true; + break; + } + } + if (!found) result.add(elementA); + } + + return result; + } + + public static int length(byte[]... bytes) { + int result = 0; + for (byte[] array : bytes) { + result += (array == null) ? 0 : array.length; + } + return result; + } + + public static byte[] intsToBytes(int[] arr, boolean bigEndian) { + byte[] ret = new byte[arr.length * 4]; + intsToBytes(arr, ret, bigEndian); + return ret; + } + + public static int[] bytesToInts(byte[] arr, boolean bigEndian) { + int[] ret = new int[arr.length / 4]; + bytesToInts(arr, ret, bigEndian); + return ret; + } + + public static void bytesToInts(byte[] b, int[] arr, boolean bigEndian) { + if (!bigEndian) { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = b[off++] & 0x000000FF; + ii |= (b[off++] << 8) & 0x0000FF00; + ii |= (b[off++] << 16) & 0x00FF0000; + ii |= (b[off++] << 24); + arr[i] = ii; + } + } else { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = b[off++] << 24; + ii |= (b[off++] << 16) & 0x00FF0000; + ii |= (b[off++] << 8) & 0x0000FF00; + ii |= b[off++] & 0x000000FF; + arr[i] = ii; + } + } + } + + public static void intsToBytes(int[] arr, byte[] b, boolean bigEndian) { + if (!bigEndian) { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = arr[i]; + b[off++] = (byte) (ii & 0xFF); + b[off++] = (byte) ((ii >> 8) & 0xFF); + b[off++] = (byte) ((ii >> 16) & 0xFF); + b[off++] = (byte) ((ii >> 24) & 0xFF); + } + } else { + int off = 0; + for (int i = 0; i < arr.length; i++) { + int ii = arr[i]; + b[off++] = (byte) ((ii >> 24) & 0xFF); + b[off++] = (byte) ((ii >> 16) & 0xFF); + b[off++] = (byte) ((ii >> 8) & 0xFF); + b[off++] = (byte) (ii & 0xFF); + } + } + } + + public static short bigEndianToShort(byte[] bs) { + return bigEndianToShort(bs, 0); + } + + public static short bigEndianToShort(byte[] bs, int off) { + int n = bs[off] << 8; + ++off; + n |= bs[off] & 0xFF; + return (short) n; + } + + public static byte[] shortToBytes(short n) { + return ByteBuffer.allocate(2).putShort(n).array(); + } + + /** + * Converts string hex representation to data bytes + * Accepts following hex: + * - with or without 0x prefix + * - with no leading 0, like 0xabc -> 0x0abc + * @param data String like '0xa5e..' or just 'a5e..' + * @return decoded bytes array + */ + public static byte[] hexStringToBytes(String data) { + if (data == null) return EMPTY_BYTE_ARRAY; + if (data.startsWith("0x")) data = data.substring(2); + if (data.length() % 2 == 1) data = "0" + data; + return Hex.decode(data); + } + + /** + * Converts string representation of host/ip to 4-bytes byte[] IPv4 + */ + public static byte[] hostToBytes(String ip) { + byte[] bytesIp; + try { + bytesIp = InetAddress.getByName(ip).getAddress(); + } catch (UnknownHostException e) { + bytesIp = new byte[4]; // fall back to invalid 0.0.0.0 address + } + + return bytesIp; + } + + /** + * Converts 4 bytes IPv4 IP to String representation + */ + public static String bytesToIp(byte[] bytesIp) { + + StringBuilder sb = new StringBuilder(); + sb.append(bytesIp[0] & 0xFF); + sb.append("."); + sb.append(bytesIp[1] & 0xFF); + sb.append("."); + sb.append(bytesIp[2] & 0xFF); + sb.append("."); + sb.append(bytesIp[3] & 0xFF); + + String ip = sb.toString(); + return ip; + } + + /** + * Returns a number of zero bits preceding the highest-order ("leftmost") one-bit + * interpreting input array as a big-endian integer value + */ + public static int numberOfLeadingZeros(byte[] bytes) { + + int i = firstNonZeroByte(bytes); + + if (i == -1) { + return bytes.length * 8; + } else { + int byteLeadingZeros = Integer.numberOfLeadingZeros((int)bytes[i] & 0xff) - 24; + return i * 8 + byteLeadingZeros; + } + } + + /** + * Parses fixed number of bytes starting from {@code offset} in {@code input} array. + * If {@code input} has not enough bytes return array will be right padded with zero bytes. + * I.e. if {@code offset} is higher than {@code input.length} then zero byte array of length {@code len} will be returned + */ + public static byte[] parseBytes(byte[] input, int offset, int len) { + + if (offset >= input.length || len == 0) + return EMPTY_BYTE_ARRAY; + + byte[] bytes = new byte[len]; + System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len)); + return bytes; + } + + /** + * Parses 32-bytes word from given input. + * Uses {@link #parseBytes(byte[], int, int)} method, + * thus, result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + */ + public static byte[] parseWord(byte[] input, int idx) { + return parseBytes(input, 32 * idx, 32); + } + + /** + * Parses 32-bytes word from given input. + * Uses {@link #parseBytes(byte[], int, int)} method, + * thus, result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + * @param offset an offset in {@code input} array to start parsing from + */ + public static byte[] parseWord(byte[] input, int offset, int idx) { + return parseBytes(input, offset + 32 * idx, 32); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/transaction/util/HashUtil.java b/core/src/main/java/org/platon/core/transaction/util/HashUtil.java new file mode 100644 index 0000000..8ce31c4 --- /dev/null +++ b/core/src/main/java/org/platon/core/transaction/util/HashUtil.java @@ -0,0 +1,13 @@ +package org.platon.core.transaction.util; + +import org.bouncycastle.jcajce.provider.digest.SHA3; + +public class HashUtil { + + public static byte[] EMPTY_ROOT = bcSHA3Digest256(new byte[0]); + // bouncycastle SHA3 + public static byte[] bcSHA3Digest256(byte[] value) { + SHA3.DigestSHA3 digestSHA3 = new SHA3.Digest256(); + return digestSHA3.digest(value); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/utils/CompactEncoder.java b/core/src/main/java/org/platon/core/utils/CompactEncoder.java new file mode 100644 index 0000000..cec8199 --- /dev/null +++ b/core/src/main/java/org/platon/core/utils/CompactEncoder.java @@ -0,0 +1,156 @@ +package org.platon.core.utils; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Arrays.copyOf; +import static java.util.Arrays.copyOfRange; +import static org.platon.common.utils.ByteUtil.appendByte; +import static org.spongycastle.util.Arrays.concatenate; +import static org.spongycastle.util.encoders.Hex.encode; + +/** + * Compact encoding of hex sequence with optional terminator + * + * The traditional compact way of encoding a hex string is to convert it into binary + * - that is, a string like 0f1248 would become three bytes 15, 18, 72. However, + * this approach has one slight problem: what if the length of the hex string is odd? + * In that case, there is no way to distinguish between, say, 0f1248 and f1248. + * + * Additionally, our application in the Merkle Patricia tree requires the additional feature + * that a hex string can also have a special "terminator symbol" at the end (denoted by the 'T'). + * A terminator symbol can occur only once, and only at the end. + * + * An alternative way of thinking about this to not think of there being a terminator symbol, + * but instead treat bit specifying the existence of the terminator symbol as a bit specifying + * that the given node encodes a final node, where the value is an actual value, rather than + * the hash of yet another node. + * + * To solve both of these issues, we force the first nibble of the final byte-stream to encode + * two flags, specifying oddness of length (ignoring the 'T' symbol) and terminator status; + * these are placed, respectively, into the two lowest significant bits of the first nibble. + * In the case of an even-length hex string, we must introduce a second nibble (of value zero) + * to ensure the hex-string is even in length and thus is representable by a whole number of bytes. + * + * Examples: + * > [ 1, 2, 3, 4, 5 ] + * '\x11\x23\x45' + * > [ 0, 1, 2, 3, 4, 5 ] + * '\x00\x01\x23\x45' + * > [ 0, 15, 1, 12, 11, 8, T ] + * '\x20\x0f\x1c\xb8' + * > [ 15, 1, 12, 11, 8, T ] + * '\x3f\x1c\xb8' + */ +public class CompactEncoder { + + private final static byte TERMINATOR = 16; + private final static Map hexMap = new HashMap<>(); + + static { + hexMap.put('0', (byte) 0x0); + hexMap.put('1', (byte) 0x1); + hexMap.put('2', (byte) 0x2); + hexMap.put('3', (byte) 0x3); + hexMap.put('4', (byte) 0x4); + hexMap.put('5', (byte) 0x5); + hexMap.put('6', (byte) 0x6); + hexMap.put('7', (byte) 0x7); + hexMap.put('8', (byte) 0x8); + hexMap.put('9', (byte) 0x9); + hexMap.put('a', (byte) 0xa); + hexMap.put('b', (byte) 0xb); + hexMap.put('c', (byte) 0xc); + hexMap.put('d', (byte) 0xd); + hexMap.put('e', (byte) 0xe); + hexMap.put('f', (byte) 0xf); + } + + /** + * Pack nibbles to binary + * + * @param nibbles sequence. may have a terminator + * @return hex-encoded byte array + */ + public static byte[] packNibbles(byte[] nibbles) { + int terminator = 0; + + if (nibbles[nibbles.length - 1] == TERMINATOR) { + terminator = 1; + nibbles = copyOf(nibbles, nibbles.length - 1); + } + int oddlen = nibbles.length % 2; + int flag = 2 * terminator + oddlen; + if (oddlen != 0) { + byte[] flags = new byte[]{(byte) flag}; + nibbles = concatenate(flags, nibbles); + } else { + byte[] flags = new byte[]{(byte) flag, 0}; + nibbles = concatenate(flags, nibbles); + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < nibbles.length; i += 2) { + buffer.write(16 * nibbles[i] + nibbles[i + 1]); + } + return buffer.toByteArray(); + } + + public static boolean hasTerminator(byte[] packedKey) { + return ((packedKey[0] >> 4) & 2) != 0; + } + + /** + * Unpack a binary string to its nibbles equivalent + * + * @param str of binary data + * @return array of nibbles in byte-format + */ + public static byte[] unpackToNibbles(byte[] str) { + byte[] base = binToNibbles(str); + base = copyOf(base, base.length - 1); + if (base[0] >= 2) { + base = appendByte(base, TERMINATOR); + } + if (base[0] % 2 == 1) { + base = copyOfRange(base, 1, base.length); + } else { + base = copyOfRange(base, 2, base.length); + } + return base; + } + + /** + * Transforms a binary array to hexadecimal format + terminator + * + * @param str byte[] + * @return array with each individual nibble adding a terminator at the end + */ + public static byte[] binToNibbles(byte[] str) { + + byte[] hexEncoded = encode(str); + byte[] hexEncodedTerminated = Arrays.copyOf(hexEncoded, hexEncoded.length + 1); + + for (int i = 0; i < hexEncoded.length; ++i){ + byte b = hexEncodedTerminated[i]; + hexEncodedTerminated[i] = hexMap.get((char) b); + } + + hexEncodedTerminated[hexEncodedTerminated.length - 1] = TERMINATOR; + return hexEncodedTerminated; + } + + + public static byte[] binToNibblesNoTerminator(byte[] str) { + + byte[] hexEncoded = encode(str); + + for (int i = 0; i < hexEncoded.length; ++i){ + byte b = hexEncoded[i]; + hexEncoded[i] = hexMap.get((char) b); + } + + return hexEncoded; + } +} diff --git a/core/src/main/java/org/platon/core/utils/DecodeResult.java b/core/src/main/java/org/platon/core/utils/DecodeResult.java new file mode 100644 index 0000000..a1e618a --- /dev/null +++ b/core/src/main/java/org/platon/core/utils/DecodeResult.java @@ -0,0 +1,44 @@ +package org.platon.core.utils; + +import org.spongycastle.util.encoders.Hex; + +import java.io.Serializable; + +@SuppressWarnings("serial") +public class DecodeResult implements Serializable { + + private int pos; + private Object decoded; + + public DecodeResult(int pos, Object decoded) { + this.pos = pos; + this.decoded = decoded; + } + + public int getPos() { + return pos; + } + + public Object getDecoded() { + return decoded; + } + + public String toString() { + return asString(this.decoded); + } + + private String asString(Object decoded) { + if (decoded instanceof String) { + return (String) decoded; + } else if (decoded instanceof byte[]) { + return Hex.toHexString((byte[]) decoded); + } else if (decoded instanceof Object[]) { + String result = ""; + for (Object item : (Object[]) decoded) { + result += asString(item); + } + return result; + } + throw new RuntimeException("Not a valid type. Should not occur"); + } +} diff --git a/core/src/main/java/org/platon/core/utils/TransactionSortedSet.java b/core/src/main/java/org/platon/core/utils/TransactionSortedSet.java new file mode 100644 index 0000000..5016126 --- /dev/null +++ b/core/src/main/java/org/platon/core/utils/TransactionSortedSet.java @@ -0,0 +1,18 @@ +package org.platon.core.utils; + +import org.platon.common.utils.ByteComparator; +import org.platon.core.transaction.Transaction; + +import java.util.TreeSet; + +public class TransactionSortedSet extends TreeSet { + public TransactionSortedSet() { + super((tx1, tx2) -> { + long refBlockNumDiff = tx1.getReferenceBlockNum() - tx2.getReferenceBlockNum(); + if (refBlockNumDiff != 0) { + return refBlockNumDiff > 0 ? 1 : -1; + } + return ByteComparator.compareTo(tx1.getHash(), 0, 32, tx2.getHash(), 0, 32); + }); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/utils/Utils.java b/core/src/main/java/org/platon/core/utils/Utils.java new file mode 100644 index 0000000..97bb077 --- /dev/null +++ b/core/src/main/java/org/platon/core/utils/Utils.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.utils; + +import org.bouncycastle.util.encoders.DecoderException; +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import java.lang.reflect.Array; +import java.math.BigInteger; +import java.net.URL; +import java.security.SecureRandom; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +public class Utils { + private static final DataWord DIVISOR = DataWord.of(64); + + private static SecureRandom random = new SecureRandom(); + + /** + * @param number should be in form '0x34fabd34....' + * @return String + */ + public static BigInteger unifiedNumericToBigInteger(String number) { + + boolean match = Pattern.matches("0[xX][0-9a-fA-F]+", number); + if (!match) + return (new BigInteger(number)); + else{ + number = number.substring(2); + number = number.length() % 2 != 0 ? "0".concat(number) : number; + byte[] numberBytes = Hex.decode(number); + return (new BigInteger(1, numberBytes)); + } + } + + /** + * Return formatted Date String: yyyy.MM.dd HH:mm:ss + * Based on Unix's time() input in seconds + * + * @param timestamp seconds since start of Unix-time + * @return String formatted as - yyyy.MM.dd HH:mm:ss + */ + public static String longToDateTime(long timestamp) { + Date date = new Date(timestamp * 1000); + DateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); + return formatter.format(date); + } + + public static String longToTimePeriod(long msec) { + if (msec < 1000) return msec + "ms"; + if (msec < 3000) return String.format("%.2fs", msec / 1000d); + if (msec < 60 * 1000) return (msec / 1000) + "s"; + long sec = msec / 1000; + if (sec < 5 * 60) return (sec / 60) + "m" + (sec % 60) + "s"; + long min = sec / 60; + if (min < 60) return min + "m"; + long hour = min / 60; + if (min < 24 * 60) return hour + "h" + (min % 60) + "m"; + long day = hour / 24; + return day + "d" + (hour % 24) + "h"; + } + + public static ImageIcon getImageIcon(String resource) { + URL imageURL = ClassLoader.getSystemResource(resource); + ImageIcon image = new ImageIcon(imageURL); + return image; + } + + static BigInteger _1000_ = new BigInteger("1000"); + + public static String getValueShortString(BigInteger number) { + BigInteger result = number; + int pow = 0; + while (result.compareTo(_1000_) == 1 || result.compareTo(_1000_) == 0) { + result = result.divide(_1000_); + pow += 3; + } + return result.toString() + "\u00b7(" + "10^" + pow + ")"; + } + + /** + * Decodes a hex string to address bytes and checks validity + * + * @param hex - a hex string of the address, e.g., 6c386a4b26f73c802f34673f7248bb118f97424a + * @return - decode and validated address byte[] + */ + public static byte[] addressStringToBytes(String hex) { + final byte[] addr; + try { + addr = Hex.decode(hex); + } catch (DecoderException addressIsNotValid) { + return null; + } + + if (isValidAddress(addr)) + return addr; + return null; + } + + public static boolean isValidAddress(byte[] addr) { + return addr != null && addr.length == 20; + } + + /** + * @param addr length should be 20 + * @return short string represent 1f21c... + */ + public static String getAddressShortString(byte[] addr) { + + if (!isValidAddress(addr)) throw new Error("not an address"); + + String addrShort = Hex.toHexString(addr, 0, 3); + + StringBuffer sb = new StringBuffer(); + sb.append(addrShort); + sb.append("..."); + + return sb.toString(); + } + + public static SecureRandom getRandom() { + return random; + } + + public static double JAVA_VERSION = getJavaVersion(); + + static double getJavaVersion() { + String version = System.getProperty("java.version"); + + // on android this property equals to 0 + if (version.equals("0")) return 0; + + int pos = 0, count = 0; + for (; pos < version.length() && count < 2; pos++) { + if (version.charAt(pos) == '.') count++; + } + return Double.parseDouble(version.substring(0, pos - 1)); + } + + public static String getHashListShort(List blockHashes) { + if (blockHashes.isEmpty()) return "[]"; + + StringBuilder sb = new StringBuilder(); + String firstHash = Hex.toHexString(blockHashes.get(0)); + String lastHash = Hex.toHexString(blockHashes.get(blockHashes.size() - 1)); + return sb.append(" ").append(firstHash).append("...").append(lastHash).toString(); + } + + public static String getNodeIdShort(String nodeId) { + return nodeId == null ? "" : nodeId.substring(0, 8); + } + + public static long toUnixTime(long javaTime) { + return javaTime / 1000; + } + + public static long fromUnixTime(long unixTime) { + return unixTime * 1000; + } + + public static T[] mergeArrays(T[] ... arr) { + int size = 0; + for (T[] ts : arr) { + size += ts.length; + } + T[] ret = (T[]) Array.newInstance(arr[0].getClass().getComponentType(), size); + int off = 0; + for (T[] ts : arr) { + System.arraycopy(ts, 0, ret, off, ts.length); + off += ts.length; + } + return ret; + } + + public static String align(String s, char fillChar, int targetLen, boolean alignRight) { + if (targetLen <= s.length()) return s; + String alignString = repeat("" + fillChar, targetLen - s.length()); + return alignRight ? alignString + s : s + alignString; + + } + public static String repeat(String s, int n) { + if (s.length() == 1) { + byte[] bb = new byte[n]; + Arrays.fill(bb, s.getBytes()[0]); + return new String(bb); + } else { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < n; i++) ret.append(s); + return ret.toString(); + } + } + + //TODO dbsource + /* public static List dumpKeys(DbSource ds) { + + ArrayList keys = new ArrayList<>(); + + for (byte[] key : ds.keys()) { + keys.add(ByteUtil.wrap(key)); + } + Collections.sort(keys); + return keys; + }*/ + + public static DataWord allButOne64th(DataWord dw) { + DataWord divResult = dw.div(DIVISOR); + return dw.sub(divResult); + } + + /** + * Show std err messages in red and throw RuntimeException to stop execution. + */ + public static void showErrorAndExit(String message, String... messages) { + LoggerFactory.getLogger("general").error(message); + final String ANSI_RED = "\u001B[31m"; + final String ANSI_RESET = "\u001B[0m"; + + System.err.println(ANSI_RED); + System.err.println(""); + System.err.println(" " + message); + for (String msg : messages) { + System.err.println(" " + msg); + } + System.err.println(""); + System.err.println(ANSI_RESET); + + throw new RuntimeException(message); + } + + /** + * Show std warning messages in red. + */ + public static void showWarn(String message, String... messages) { + LoggerFactory.getLogger("general").warn(message); + final String ANSI_RED = "\u001B[31m"; + final String ANSI_RESET = "\u001B[0m"; + + System.err.println(ANSI_RED); + System.err.println(""); + System.err.println(" " + message); + for (String msg : messages) { + System.err.println(" " + msg); + } + System.err.println(""); + System.err.println(ANSI_RESET); + } + + public static String sizeToStr(long size) { + if (size < 2 * (1L << 10)) return size + "b"; + if (size < 2 * (1L << 20)) return String.format("%dKb", size / (1L << 10)); + if (size < 2 * (1L << 30)) return String.format("%dMb", size / (1L << 20)); + return String.format("%dGb", size / (1L << 30)); + } + + public static void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public static boolean isHexEncoded(String value) { + if (value == null) return false; + if ("".equals(value)) return true; + + try { + //noinspection ResultOfMethodCallIgnored + new BigInteger(value, 16); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static void transfer(Repository repository, byte[] fromAddr, byte[] toAddr, BigInteger value){ + repository.addBalance(fromAddr, value.negate()); + repository.addBalance(toAddr, value); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/validator/TimelinessValidator.java b/core/src/main/java/org/platon/core/validator/TimelinessValidator.java new file mode 100644 index 0000000..58c3552 --- /dev/null +++ b/core/src/main/java/org/platon/core/validator/TimelinessValidator.java @@ -0,0 +1,165 @@ +package org.platon.core.validator; + +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.LRUHashMap; +import org.platon.core.config.CoreConfig; +import org.platon.core.validator.model.ValidateBlock; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +/** + * TimelinessValidator + * + * @author yanze + * @desc check block and tx is valid + * @create 2018-08-08 10:58 + **/ +public class TimelinessValidator { + + private LRUHashMap ancestorBlockCache; + //TODO pending block and tx may be merge with pool + private LRUHashMap pendingBlockCache; + private ArrayDeque pendingTxCache; + private long maxAncestorBlockNum; + + public TimelinessValidator() { + this.ancestorBlockCache = new LRUHashMap<>(Long.parseLong(CoreConfig.getInstance().ancestorBlockCacheSize())); + this.pendingBlockCache = new LRUHashMap<>(Long.parseLong(CoreConfig.getInstance().pendingBlockCacheSize())); + this.pendingTxCache = new ArrayDeque<>(Integer.parseInt(CoreConfig.getInstance().pendingTxCacheSize())); + this.maxAncestorBlockNum = 0; + } + + public TimelinessValidator(Map map, long maxAncestorBlockNum) { + this.ancestorBlockCache = new LRUHashMap<>(Long.parseLong(CoreConfig.getInstance().ancestorBlockCacheSize())); + this.pendingBlockCache = new LRUHashMap<>(Long.parseLong(CoreConfig.getInstance().pendingBlockCacheSize())); + this.pendingTxCache = new ArrayDeque<>(Integer.parseInt(CoreConfig.getInstance().pendingTxCacheSize())); + for(Long num : map.keySet()){ + ValidateBlock temp = map.get(num); + //deep clone + ancestorBlockCache.put(num,temp.clone()); + } + this.maxAncestorBlockNum = maxAncestorBlockNum; + } + + + /** + * validate block hash + * @param blockHash + * @param ancestorBlockNum + * @param ancestorBlockHash + * @return + */ + public boolean validateBlockHash(byte[] blockHash,long ancestorBlockNum,byte[] ancestorBlockHash){ + if(maxAncestorBlockNum == 0 && blockHash != null + && ancestorBlockNum == 0 && ancestorBlockHash == null){ + return true; + } + ValidateBlock ancestorBlock = ancestorBlockCache.get(ancestorBlockNum); + //validate ancestorBlockNum、ancestorBlockHash + if(ancestorBlock == null||ancestorBlock.getBlockHash().length==0 + || !Arrays.equals(ancestorBlock.getBlockHash(),ancestorBlockHash)){ + return false; + } + //validate blockHash step1:check blockHash is exist in ancestorBlock + for(long i = ancestorBlockNum;i <= maxAncestorBlockNum;i++){ + if(Arrays.equals(ancestorBlockCache.get(i).getBlockHash(),blockHash)){ + return false; + } + } + //validate blockHash step2:check blockHash is exist in pendingBlock + ValidateBlock pendingBlock = pendingBlockCache.get(new ByteArrayWrapper(blockHash)); + if(pendingBlock != null){ + return false; + } + return true; + } + + /** + * add block hash in pending block cache + * @param blockHash + * @param ancestorBlockNum + * @param ancestorBlockHash + * @param txSet + * @return + */ + public boolean addBlockHash(byte[] blockHash, long ancestorBlockNum, byte[] ancestorBlockHash, HashSet txSet){ + if(this.validateBlockHash(blockHash,ancestorBlockNum,ancestorBlockHash)){ + ValidateBlock pendingBlock = new ValidateBlock(blockHash,ancestorBlockNum,ancestorBlockHash,txSet); + pendingBlockCache.put(new ByteArrayWrapper(blockHash),pendingBlock); + return true; + } + return false; + } + /** + * add block hash in ancestor block cache + * @param blockHash + * @param blockNum + * @param ancestorBlockNum + * @param ancestorBlockHash + * @param txSet + * @return + */ + public boolean addAncestorBlockHash(byte[] blockHash,long blockNum,byte[] ancestorBlockHash,long ancestorBlockNum,HashSet txSet){ + if(pendingBlockCache.get(blockHash) == null){ + if(!validateBlockHash(blockHash,ancestorBlockNum,ancestorBlockHash)){ + return false; + } + } + ValidateBlock block = new ValidateBlock(blockHash, ancestorBlockNum, ancestorBlockHash, txSet); + pendingBlockCache.remove(blockHash); + //??? did I add method about pendingTxCache.remove + ancestorBlockCache.put(blockNum, block); + maxAncestorBlockNum = blockNum; + return true; + } + + /** + * validate tx hash + * @param txHash + * @param ancestorBlockNum + * @param ancestorBlockHash + * @return + */ + public boolean validateTxHash(byte[] txHash,long ancestorBlockNum,byte[] ancestorBlockHash){ + if(maxAncestorBlockNum == 0 && txHash != null + && ancestorBlockNum == 0 && ancestorBlockHash == null){ + return true; + } + ValidateBlock ancestorBlock = ancestorBlockCache.get(ancestorBlockNum); + //validate ancestorBlockNum、ancestorBlockHash + if(ancestorBlock == null||ancestorBlock.getBlockHash().length==0 + || !Arrays.equals(ancestorBlock.getBlockHash(),ancestorBlockHash)){ + return false; + } + //validate pengdingTxcache exist + if(txHash == null || pendingTxCache.contains(new ByteArrayWrapper(txHash))){ + return false; + } + //validate ancestorBlock tx exist + for (long i = ancestorBlockNum;i <= maxAncestorBlockNum;i++){ + ValidateBlock blockTemp = ancestorBlockCache.get(i); + if(blockTemp.getTxHashSet().contains(new ByteArrayWrapper(txHash))){ + return false; + } + } + return true; + } + + /** + * add tx hash in pending tx cache + * @param txHash + * @param ancestorBlockNum + * @param ancestorBlockHash + * @return + */ + public boolean addTxHash(byte[] txHash,long ancestorBlockNum,byte[] ancestorBlockHash){ + if(this.validateTxHash(txHash,ancestorBlockNum,ancestorBlockHash)){ + pendingTxCache.add(new ByteArrayWrapper(txHash)); + return true; + } + return false; + } +} diff --git a/core/src/main/java/org/platon/core/validator/model/ValidateBlock.java b/core/src/main/java/org/platon/core/validator/model/ValidateBlock.java new file mode 100644 index 0000000..ebd179b --- /dev/null +++ b/core/src/main/java/org/platon/core/validator/model/ValidateBlock.java @@ -0,0 +1,63 @@ +package org.platon.core.validator.model; + +import org.platon.common.utils.ByteArrayWrapper; + +import java.util.HashSet; + +/** + * ValidateBlock + * + * @author yanze + * @desc block model for validate + * @create 2018-08-08 11:09 + **/ +public class ValidateBlock { + + private byte[] blockHash; + private long ancestorBlockNum; + private byte[] ancestorBlockHash; + private HashSet txHashSet; + + public ValidateBlock(byte[] blockHash, long ancestorBlockNum, byte[] ancestorBlockHash, HashSet txHashSet) { + this.blockHash = blockHash; + this.ancestorBlockNum = ancestorBlockNum; + this.ancestorBlockHash = ancestorBlockHash; + this.txHashSet = txHashSet; + } + + public byte[] getBlockHash() { + return blockHash; + } + + public void setBlockHash(byte[] blockHash) { + this.blockHash = blockHash; + } + + public long getAncestorBlockNum() { + return ancestorBlockNum; + } + + public void setAncestorBlockNum(long ancestorBlockNum) { + this.ancestorBlockNum = ancestorBlockNum; + } + + public byte[] getAncestorBlockHash() { + return ancestorBlockHash; + } + + public void setAncestorBlockHash(byte[] ancestorBlockHash) { + this.ancestorBlockHash = ancestorBlockHash; + } + + public HashSet getTxHashSet() { + return txHashSet; + } + + public void setTxHashSet(HashSet txHashSet) { + this.txHashSet = txHashSet; + } + + public ValidateBlock clone(){ + return new ValidateBlock(this.blockHash,this.ancestorBlockNum,this.ancestorBlockHash,this.txHashSet); + } +} diff --git a/core/src/main/java/org/platon/core/vm/CallCreate.java b/core/src/main/java/org/platon/core/vm/CallCreate.java new file mode 100644 index 0000000..9408063 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/CallCreate.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +/** + * @author Roman Mandeleil + * @since 03.07.2014 + */ +public class CallCreate { + + final byte[] data; + final byte[] destination; + final byte[] gasLimit; + final byte[] value; + + + public CallCreate(byte[] data, byte[] destination, byte[] gasLimit, byte[] value) { + this.data = data; + this.destination = destination; + this.gasLimit = gasLimit; + this.value = value; + } + + public byte[] getData() { + return data; + } + + public byte[] getDestination() { + return destination; + } + + public byte[] getGasLimit() { + return gasLimit; + } + + public byte[] getValue() { + return value; + } +} diff --git a/core/src/main/java/org/platon/core/vm/EnergonCost.java b/core/src/main/java/org/platon/core/vm/EnergonCost.java new file mode 100644 index 0000000..3cd09d1 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/EnergonCost.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +/** + * The fundamental network cost unit. Paid for exclusively by Ether, which is converted + * freely to and from ENERGON as required. ENERGON does not exist outside of the internal Ethereum + * computation engine; its price is set by the Transaction and miners are free to + * ignore Transactions whose ENERGON price is too low. + */ +public class EnergonCost { + + /* backwards compatibility, remove eventually */ + private final int STEP = 1; + private final int SSTORE = 300; + /* backwards compatibility, remove eventually */ + + private final int ZEROSTEP = 0; + private final int QUICKSTEP = 2; + private final int FASTESTSTEP = 3; + private final int FASTSTEP = 5; + private final int MIDSTEP = 8; + private final int SLOWSTEP = 10; + private final int EXTSTEP = 20; + + private final int GENESISENERGONLIMIT = 1000000; + private final int MINENERGONLIMIT = 125000; + + private final int BALANCE = 20; + private final int SHA3 = 30; + private final int SHA3_WORD = 6; + private final int SLOAD = 50; + private final int STOP = 0; + private final int SUICIDE = 0; + private final int CLEAR_SSTORE = 5000; + private final int SET_SSTORE = 20000; + private final int RESET_SSTORE = 5000; + private final int REFUND_SSTORE = 15000; + private final int CREATE = 32000; + + private final int JUMPDEST = 1; + private final int CREATE_DATA_BYTE = 5; + private final int CALL = 40; + private final int STIPEND_CALL = 2300; + private final int VT_CALL = 9000; //value transfer call + private final int NEW_ACCT_CALL = 25000; //new account call + private final int MEMORY = 3; + private final int SUICIDE_REFUND = 24000; + private final int QUAD_COEFF_DIV = 512; + private final int CREATE_DATA = 200; + private final int TX_NO_ZERO_DATA = 68; + private final int TX_ZERO_DATA = 4; + private final int TRANSACTION = 21000; + private final int TRANSACTION_CREATE_CONTRACT = 53000; + private final int LOG_ENERGON = 375; + private final int LOG_DATA_ENERGON = 8; + private final int LOG_TOPIC_ENERGON = 375; + private final int COPY_ENERGON = 3; + private final int EXP_ENERGON = 10; + private final int EXP_BYTE_ENERGON = 10; + private final int IDENTITY = 15; + private final int IDENTITY_WORD = 3; + private final int RIPEMD160 = 600; + private final int RIPEMD160_WORD = 120; + private final int SHA256 = 60; + private final int SHA256_WORD = 12; + private final int EC_RECOVER = 3000; + private final int EXT_CODE_SIZE = 20; + private final int EXT_CODE_COPY = 20; + private final int EXT_CODE_HASH = 400; + private final int NEW_ACCT_SUICIDE = 0; + + public int getSTEP() { + return STEP; + } + + public int getSSTORE() { + return SSTORE; + } + + public int getZEROSTEP() { + return ZEROSTEP; + } + + public int getQUICKSTEP() { + return QUICKSTEP; + } + + public int getFASTESTSTEP() { + return FASTESTSTEP; + } + + public int getFASTSTEP() { + return FASTSTEP; + } + + public int getMIDSTEP() { + return MIDSTEP; + } + + public int getSLOWSTEP() { + return SLOWSTEP; + } + + public int getEXTSTEP() { + return EXTSTEP; + } + + public int getGENESISENERGONLIMIT() { + return GENESISENERGONLIMIT; + } + + public int getMINENERGONLIMIT() { + return MINENERGONLIMIT; + } + + public int getBALANCE() { + return BALANCE; + } + + public int getSHA3() { + return SHA3; + } + + public int getSHA3_WORD() { + return SHA3_WORD; + } + + public int getSLOAD() { + return SLOAD; + } + + public int getSTOP() { + return STOP; + } + + public int getSUICIDE() { + return SUICIDE; + } + + public int getCLEAR_SSTORE() { + return CLEAR_SSTORE; + } + + public int getSET_SSTORE() { + return SET_SSTORE; + } + + public int getRESET_SSTORE() { + return RESET_SSTORE; + } + + public int getREFUND_SSTORE() { + return REFUND_SSTORE; + } + + public int getCREATE() { + return CREATE; + } + + public int getJUMPDEST() { + return JUMPDEST; + } + + public int getCREATE_DATA_BYTE() { + return CREATE_DATA_BYTE; + } + + public int getCALL() { + return CALL; + } + + public int getSTIPEND_CALL() { + return STIPEND_CALL; + } + + public int getVT_CALL() { + return VT_CALL; + } + + public int getNEW_ACCT_CALL() { + return NEW_ACCT_CALL; + } + + public int getNEW_ACCT_SUICIDE() { + return NEW_ACCT_SUICIDE; + } + + public int getMEMORY() { + return MEMORY; + } + + public int getSUICIDE_REFUND() { + return SUICIDE_REFUND; + } + + public int getQUAD_COEFF_DIV() { + return QUAD_COEFF_DIV; + } + + public int getCREATE_DATA() { + return CREATE_DATA; + } + + public int getTX_NO_ZERO_DATA() { + return TX_NO_ZERO_DATA; + } + + public int getTX_ZERO_DATA() { + return TX_ZERO_DATA; + } + + public int getTRANSACTION() { + return TRANSACTION; + } + + public int getTRANSACTION_CREATE_CONTRACT() { + return TRANSACTION_CREATE_CONTRACT; + } + + public int getLOG_ENERGON() { + return LOG_ENERGON; + } + + public int getLOG_DATA_ENERGON() { + return LOG_DATA_ENERGON; + } + + public int getLOG_TOPIC_ENERGON() { + return LOG_TOPIC_ENERGON; + } + + public int getCOPY_ENERGON() { + return COPY_ENERGON; + } + + public int getEXP_ENERGON() { + return EXP_ENERGON; + } + + public int getEXP_BYTE_ENERGON() { + return EXP_BYTE_ENERGON; + } + + public int getIDENTITY() { + return IDENTITY; + } + + public int getIDENTITY_WORD() { + return IDENTITY_WORD; + } + + public int getRIPEMD160() { + return RIPEMD160; + } + + public int getRIPEMD160_WORD() { + return RIPEMD160_WORD; + } + + public int getSHA256() { + return SHA256; + } + + public int getSHA256_WORD() { + return SHA256_WORD; + } + + public int getEC_RECOVER() { + return EC_RECOVER; + } + + public int getEXT_CODE_SIZE() { + return EXT_CODE_SIZE; + } + + public int getEXT_CODE_COPY() { + return EXT_CODE_COPY; + } + + public int getEXT_CODE_HASH() { + return EXT_CODE_HASH; + } +} diff --git a/core/src/main/java/org/platon/core/vm/MessageCall.java b/core/src/main/java/org/platon/core/vm/MessageCall.java new file mode 100644 index 0000000..cd83a92 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/MessageCall.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +import org.platon.common.wrapper.DataWord; + +/** + * A wrapper for a message call from a contract to another account. + * This can either be a normal CALL, CALLCODE, DELEGATECALL or POST call. + */ +public class MessageCall { + + /** + * Type of internal call. Either CALL, CALLCODE or POST + */ + private final OpCode type; + + /** + * Energon to pay for the call, remaining Energon will be refunded to the caller + */ + private final DataWord energon; + /** + * address of account which code to call + */ + private final DataWord codeAddress; + /** + * the value that can be transfer along with the code execution + */ + private final DataWord endowment; + /** + * start of memory to be input data to the call + */ + private final DataWord inDataOffs; + /** + * size of memory to be input data to the call + */ + private final DataWord inDataSize; + /** + * start of memory to be output of the call + */ + private DataWord outDataOffs; + /** + * size of memory to be output data to the call + */ + private DataWord outDataSize; + + public MessageCall(OpCode type, DataWord Energon, DataWord codeAddress, + DataWord endowment, DataWord inDataOffs, DataWord inDataSize) { + this.type = type; + this.energon = Energon; + this.codeAddress = codeAddress; + this.endowment = endowment; + this.inDataOffs = inDataOffs; + this.inDataSize = inDataSize; + } + + public MessageCall(OpCode type, DataWord Energon, DataWord codeAddress, + DataWord endowment, DataWord inDataOffs, DataWord inDataSize, + DataWord outDataOffs, DataWord outDataSize) { + this(type, Energon, codeAddress, endowment, inDataOffs, inDataSize); + this.outDataOffs = outDataOffs; + this.outDataSize = outDataSize; + } + + public OpCode getType() { + return type; + } + + public DataWord getEnergon() { + return energon; + } + + public DataWord getCodeAddress() { + return codeAddress; + } + + public DataWord getEndowment() { + return endowment; + } + + public DataWord getInDataOffs() { + return inDataOffs; + } + + public DataWord getInDataSize() { + return inDataSize; + } + + public DataWord getOutDataOffs() { + return outDataOffs; + } + + public DataWord getOutDataSize() { + return outDataSize; + } +} diff --git a/core/src/main/java/org/platon/core/vm/OpCode.java b/core/src/main/java/org/platon/core/vm/OpCode.java new file mode 100644 index 0000000..8d2b2dd --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/OpCode.java @@ -0,0 +1,798 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import static org.platon.core.vm.OpCode.Tier.*; + + +/** + * Instruction set for the Ethereum Virtual Machine + * See Yellow Paper: http://www.gavwood.com/Paper.pdf + * - Appendix G. Virtual Machine Specification + */ +public enum OpCode { + // TODO #POC9 Need to make tiers more accurate + /** + * Halts execution (0x00) + */ + STOP(0x00, 0, 0, ZeroTier), + + /* Arithmetic Operations */ + + /** + * (0x01) Addition operation + */ + ADD(0x01, 2, 1, VeryLowTier), + /** + * (0x02) Multiplication operation + */ + MUL(0x02, 2, 1, LowTier), + /** + * (0x03) Subtraction operations + */ + SUB(0x03, 2, 1, VeryLowTier), + /** + * (0x04) Integer division operation + */ + DIV(0x04, 2, 1, LowTier), + /** + * (0x05) Signed integer division operation + */ + SDIV(0x05, 2, 1, LowTier), + /** + * (0x06) Modulo remainder operation + */ + MOD(0x06, 2, 1, LowTier), + /** + * (0x07) Signed modulo remainder operation + */ + SMOD(0x07, 2, 1, LowTier), + /** + * (0x08) Addition combined with modulo + * remainder operation + */ + ADDMOD(0x08, 3, 1, MidTier), + /** + * (0x09) Multiplication combined with modulo + * remainder operation + */ + MULMOD(0x09, 3, 1, MidTier), + /** + * (0x0a) Exponential operation + */ + EXP(0x0a, 2, 1, SpecialTier), + /** + * (0x0b) Extend length of signed integer + */ + SIGNEXTEND(0x0b, 2, 1, LowTier), + + /* Bitwise Logic & Comparison Operations */ + + /** + * (0x10) Less-than comparison + */ + LT(0X10, 2, 1, VeryLowTier), + /** + * (0x11) Greater-than comparison + */ + GT(0X11, 2, 1, VeryLowTier), + /** + * (0x12) Signed less-than comparison + */ + SLT(0X12, 2, 1, VeryLowTier), + /** + * (0x13) Signed greater-than comparison + */ + SGT(0X13, 2, 1, VeryLowTier), + /** + * (0x14) Equality comparison + */ + EQ(0X14, 2, 1, VeryLowTier), + /** + * (0x15) Negation operation + */ + ISZERO(0x15, 1, 1, VeryLowTier), + /** + * (0x16) Bitwise AND operation + */ + AND(0x16, 2, 1, VeryLowTier), + /** + * (0x17) Bitwise OR operation + */ + OR(0x17, 2, 1, VeryLowTier), + /** + * (0x18) Bitwise XOR operation + */ + XOR(0x18, 2, 1, VeryLowTier), + /** + * (0x19) Bitwise NOT operationr + */ + NOT(0x19, 1, 1, VeryLowTier), + /** + * (0x1a) Retrieve single byte from word + */ + BYTE(0x1a, 2, 1, VeryLowTier), + /** + * (0x1b) Shift left + */ + SHL(0x1b, 2, 1, VeryLowTier), + /** + * (0x1c) Logical shift right + */ + SHR(0x1c, 2, 1, VeryLowTier), + /** + * (0x1d) Arithmetic shift right + */ + SAR(0x1d, 2, 1, VeryLowTier), + + /* Cryptographic Operations */ + + /** + * (0x20) Compute SHA3-256 hash + */ + SHA3(0x20, 2, 1, SpecialTier), + + /* Environmental Information */ + + /** + * (0x30) Get address of currently + * executing account + */ + ADDRESS(0x30, 0, 1, BaseTier), + /** + * (0x31) Get balance of the given account + */ + BALANCE(0x31, 1, 1, ExtTier), + /** + * (0x32) Get execution origination address + */ + ORIGIN(0x32, 0, 1, BaseTier), + /** + * (0x33) Get caller address + */ + CALLER(0x33, 0, 1, BaseTier), + /** + * (0x34) Get deposited value by the + * instruction/transaction responsible + * for this execution + */ + CALLVALUE(0x34, 0, 1, BaseTier), + /** + * (0x35) Get input data of current + * environment + */ + CALLDATALOAD(0x35, 1, 1, VeryLowTier), + /** + * (0x36) Get size of input data in current + * environment + */ + CALLDATASIZE(0x36, 0, 1, BaseTier), + /** + * (0x37) Copy input data in current + * environment to memory + */ + CALLDATACOPY(0x37, 3, 0, VeryLowTier), + /** + * (0x38) Get size of code running in + * current environment + */ + CODESIZE(0x38, 0, 1, BaseTier), + /** + * (0x39) Copy code running in current + * environment to memory + */ + CODECOPY(0x39, 3, 0, VeryLowTier), // [len code_start mem_start CODECOPY] + + RETURNDATASIZE(0x3d, 0, 1, BaseTier), + + RETURNDATACOPY(0x3e, 3, 0, VeryLowTier), + /** + * (0x3a) Get price of gas in current + * environment + */ + GASPRICE(0x3a, 0, 1, BaseTier), + /** + * (0x3b) Get size of code running in + * current environment with given offset + */ + EXTCODESIZE(0x3b, 1, 1, ExtTier), + /** + * (0x3c) Copy code running in current + * environment to memory with given offset + */ + EXTCODECOPY(0x3c, 4, 0, ExtTier), + /** + * (0x3f) Returns the keccak256 hash of + * a contract’s code + */ + EXTCODEHASH(0x3f, 1,1 , ExtTier), + + /* Block Information */ + + /** + * (0x40) Get hash of most recent + * complete block + */ + BLOCKHASH(0x40, 1, 1, ExtTier), + /** + * (0x41) Get the block’s coinbase address + */ + COINBASE(0x41, 0, 1, BaseTier), + /** + * (x042) Get the block’s timestamp + */ + TIMESTAMP(0x42, 0, 1, BaseTier), + /** + * (0x43) Get the block’s number + */ + NUMBER(0x43, 0, 1, BaseTier), + /** + * (0x44) Get the block’s difficulty + */ + DIFFICULTY(0x44, 0, 1, BaseTier), + /** + * (0x45) Get the block’s gas limit + */ + GASLIMIT(0x45, 0, 1, BaseTier), + + /* Memory, Storage and Flow Operations */ + + /** + * (0x50) Remove item from stack + */ + POP(0x50, 1, 0, BaseTier), + /** + * (0x51) Load word from memory + */ + MLOAD(0x51, 1, 1, VeryLowTier), + /** + * (0x52) Save word to memory + */ + MSTORE(0x52, 2, 0, VeryLowTier), + /** + * (0x53) Save byte to memory + */ + MSTORE8(0x53, 2, 0, VeryLowTier), + /** + * (0x54) Load word from storage + */ + SLOAD(0x54, 1, 1, SpecialTier), + /** + * (0x55) Save word to storage + */ + SSTORE(0x55, 2, 0, SpecialTier), + /** + * (0x56) Alter the program counter + */ + JUMP(0x56, 1, 0, MidTier), + /** + * (0x57) Conditionally alter the program + * counter + */ + JUMPI(0x57, 2, 0, HighTier), + /** + * (0x58) Get the program counter + */ + PC(0x58, 0, 1, BaseTier), + /** + * (0x59) Get the size of active memory + */ + MSIZE(0x59, 0, 1, BaseTier), + /** + * (0x5a) Get the amount of available gas + */ + GAS(0x5a, 0, 1, BaseTier), + /** + * (0x5b) + */ + JUMPDEST(0x5b, 0, 0, SpecialTier), + + /* Push Operations */ + + /** + * (0x60) Place 1-byte item on stack + */ + PUSH1(0x60, 0, 1, VeryLowTier), + /** + * (0x61) Place 2-byte item on stack + */ + PUSH2(0x61, 0, 1, VeryLowTier), + /** + * (0x62) Place 3-byte item on stack + */ + PUSH3(0x62, 0, 1, VeryLowTier), + /** + * (0x63) Place 4-byte item on stack + */ + PUSH4(0x63, 0, 1, VeryLowTier), + /** + * (0x64) Place 5-byte item on stack + */ + PUSH5(0x64, 0, 1, VeryLowTier), + /** + * (0x65) Place 6-byte item on stack + */ + PUSH6(0x65, 0, 1, VeryLowTier), + /** + * (0x66) Place 7-byte item on stack + */ + PUSH7(0x66, 0, 1, VeryLowTier), + /** + * (0x67) Place 8-byte item on stack + */ + PUSH8(0x67, 0, 1, VeryLowTier), + /** + * (0x68) Place 9-byte item on stack + */ + PUSH9(0x68, 0, 1, VeryLowTier), + /** + * (0x69) Place 10-byte item on stack + */ + PUSH10(0x69, 0, 1, VeryLowTier), + /** + * (0x6a) Place 11-byte item on stack + */ + PUSH11(0x6a, 0, 1, VeryLowTier), + /** + * (0x6b) Place 12-byte item on stack + */ + PUSH12(0x6b, 0, 1, VeryLowTier), + /** + * (0x6c) Place 13-byte item on stack + */ + PUSH13(0x6c, 0, 1, VeryLowTier), + /** + * (0x6d) Place 14-byte item on stack + */ + PUSH14(0x6d, 0, 1, VeryLowTier), + /** + * (0x6e) Place 15-byte item on stack + */ + PUSH15(0x6e, 0, 1, VeryLowTier), + /** + * (0x6f) Place 16-byte item on stack + */ + PUSH16(0x6f, 0, 1, VeryLowTier), + /** + * (0x70) Place 17-byte item on stack + */ + PUSH17(0x70, 0, 1, VeryLowTier), + /** + * (0x71) Place 18-byte item on stack + */ + PUSH18(0x71, 0, 1, VeryLowTier), + /** + * (0x72) Place 19-byte item on stack + */ + PUSH19(0x72, 0, 1, VeryLowTier), + /** + * (0x73) Place 20-byte item on stack + */ + PUSH20(0x73, 0, 1, VeryLowTier), + /** + * (0x74) Place 21-byte item on stack + */ + PUSH21(0x74, 0, 1, VeryLowTier), + /** + * (0x75) Place 22-byte item on stack + */ + PUSH22(0x75, 0, 1, VeryLowTier), + /** + * (0x76) Place 23-byte item on stack + */ + PUSH23(0x76, 0, 1, VeryLowTier), + /** + * (0x77) Place 24-byte item on stack + */ + PUSH24(0x77, 0, 1, VeryLowTier), + /** + * (0x78) Place 25-byte item on stack + */ + PUSH25(0x78, 0, 1, VeryLowTier), + /** + * (0x79) Place 26-byte item on stack + */ + PUSH26(0x79, 0, 1, VeryLowTier), + /** + * (0x7a) Place 27-byte item on stack + */ + PUSH27(0x7a, 0, 1, VeryLowTier), + /** + * (0x7b) Place 28-byte item on stack + */ + PUSH28(0x7b, 0, 1, VeryLowTier), + /** + * (0x7c) Place 29-byte item on stack + */ + PUSH29(0x7c, 0, 1, VeryLowTier), + /** + * (0x7d) Place 30-byte item on stack + */ + PUSH30(0x7d, 0, 1, VeryLowTier), + /** + * (0x7e) Place 31-byte item on stack + */ + PUSH31(0x7e, 0, 1, VeryLowTier), + /** + * (0x7f) Place 32-byte (full word) + * item on stack + */ + PUSH32(0x7f, 0, 1, VeryLowTier), + + /* Duplicate Nth item from the stack */ + + /** + * (0x80) Duplicate 1st item on stack + */ + DUP1(0x80, 1, 2, VeryLowTier), + /** + * (0x81) Duplicate 2nd item on stack + */ + DUP2(0x81, 2, 3, VeryLowTier), + /** + * (0x82) Duplicate 3rd item on stack + */ + DUP3(0x82, 3, 4, VeryLowTier), + /** + * (0x83) Duplicate 4th item on stack + */ + DUP4(0x83, 4, 5, VeryLowTier), + /** + * (0x84) Duplicate 5th item on stack + */ + DUP5(0x84, 5, 6, VeryLowTier), + /** + * (0x85) Duplicate 6th item on stack + */ + DUP6(0x85, 6, 7, VeryLowTier), + /** + * (0x86) Duplicate 7th item on stack + */ + DUP7(0x86, 7, 8, VeryLowTier), + /** + * (0x87) Duplicate 8th item on stack + */ + DUP8(0x87, 8, 9, VeryLowTier), + /** + * (0x88) Duplicate 9th item on stack + */ + DUP9(0x88, 9, 10, VeryLowTier), + /** + * (0x89) Duplicate 10th item on stack + */ + DUP10(0x89, 10, 11, VeryLowTier), + /** + * (0x8a) Duplicate 11th item on stack + */ + DUP11(0x8a, 11, 12, VeryLowTier), + /** + * (0x8b) Duplicate 12th item on stack + */ + DUP12(0x8b, 12, 13, VeryLowTier), + /** + * (0x8c) Duplicate 13th item on stack + */ + DUP13(0x8c, 13, 14, VeryLowTier), + /** + * (0x8d) Duplicate 14th item on stack + */ + DUP14(0x8d, 14, 15, VeryLowTier), + /** + * (0x8e) Duplicate 15th item on stack + */ + DUP15(0x8e, 15, 16, VeryLowTier), + /** + * (0x8f) Duplicate 16th item on stack + */ + DUP16(0x8f, 16, 17, VeryLowTier), + + /* Swap the Nth item from the stack with the top */ + + /** + * (0x90) Exchange 2nd item from stack with the top + */ + SWAP1(0x90, 2, 2, VeryLowTier), + /** + * (0x91) Exchange 3rd item from stack with the top + */ + SWAP2(0x91, 3, 3, VeryLowTier), + /** + * (0x92) Exchange 4th item from stack with the top + */ + SWAP3(0x92, 4, 4, VeryLowTier), + /** + * (0x93) Exchange 5th item from stack with the top + */ + SWAP4(0x93, 5, 5, VeryLowTier), + /** + * (0x94) Exchange 6th item from stack with the top + */ + SWAP5(0x94, 6, 6, VeryLowTier), + /** + * (0x95) Exchange 7th item from stack with the top + */ + SWAP6(0x95, 7, 7, VeryLowTier), + /** + * (0x96) Exchange 8th item from stack with the top + */ + SWAP7(0x96, 8, 8, VeryLowTier), + /** + * (0x97) Exchange 9th item from stack with the top + */ + SWAP8(0x97, 9, 9, VeryLowTier), + /** + * (0x98) Exchange 10th item from stack with the top + */ + SWAP9(0x98, 10, 10, VeryLowTier), + /** + * (0x99) Exchange 11th item from stack with the top + */ + SWAP10(0x99, 11, 11,VeryLowTier), + /** + * (0x9a) Exchange 12th item from stack with the top + */ + SWAP11(0x9a, 12, 12, VeryLowTier), + /** + * (0x9b) Exchange 13th item from stack with the top + */ + SWAP12(0x9b, 13, 13, VeryLowTier), + /** + * (0x9c) Exchange 14th item from stack with the top + */ + SWAP13(0x9c, 14, 14, VeryLowTier), + /** + * (0x9d) Exchange 15th item from stack with the top + */ + SWAP14(0x9d, 15, 15, VeryLowTier), + /** + * (0x9e) Exchange 16th item from stack with the top + */ + SWAP15(0x9e, 16, 16, VeryLowTier), + /** + * (0x9f) Exchange 17th item from stack with the top + */ + SWAP16(0x9f, 17, 17, VeryLowTier), + + /** + * (0xa[n]) log some data for some addres with 0..n tags [addr [tag0..tagn] data] + */ + LOG0(0xa0, 2, 0, SpecialTier), + LOG1(0xa1, 3, 0, SpecialTier), + LOG2(0xa2, 4, 0, SpecialTier), + LOG3(0xa3, 5, 0, SpecialTier), + LOG4(0xa4, 6, 0, SpecialTier), + + /* System operations */ + + /** + * (0xf0) Create a new account with associated code + */ + CREATE(0xf0, 3, 1, SpecialTier), // [in_size] [in_offs] [gas_val] CREATE + /** + * (cxf1) Message-call into an account + */ + CALL(0xf1, 7, 1, SpecialTier, CallFlags.Call, CallFlags.HasValue), + // [out_data_size] [out_data_start] [in_data_size] [in_data_start] [value] [to_addr] + // [gas] CALL + /** + * (0xf2) Calls self, but grabbing the code from the + * TO argument instead of from one's own address + */ + CALLCODE(0xf2, 7, 1, SpecialTier, CallFlags.Call, CallFlags.HasValue, CallFlags.Stateless), + /** + * (0xf3) Halt execution returning output data + */ + RETURN(0xf3, 2, 0, ZeroTier), + + /** + * (0xf4) similar in idea to CALLCODE, except that it propagates the sender and value + * from the parent scope to the child scope, ie. the call created has the same sender + * and value as the original call. + * also the Value parameter is omitted for this opCode + */ + DELEGATECALL(0xf4, 6, 1, SpecialTier, CallFlags.Call, CallFlags.Stateless, CallFlags.Delegate), + + /** + * opcode that can be used to call another contract (or itself) while disallowing any + * modifications to the state during the call (and its subcalls, if present). + * Any opcode that attempts to perform such a modification (see below for details) + * will result in an exception instead of performing the modification. + */ + STATICCALL(0xfa, 6, 1, SpecialTier, CallFlags.Call, CallFlags.Static), + + /** + * (0xfd) The `REVERT` instruction will stop execution, roll back all state changes done so far + * and provide a pointer to a memory section, which can be interpreted as an error code or message. + * While doing so, it will not consume all the remaining gas. + */ + REVERT(0xfd, 2, 0, ZeroTier), + /** + * (0xff) Halt execution and register account for + * later deletion + */ + SUICIDE(0xff, 1, 0, ZeroTier); + + private final byte opcode; + private final int require; + private final Tier tier; + private final int ret; + private final EnumSet callFlags; + + private static final OpCode[] intToTypeMap = new OpCode[256]; + private static final Map stringToByteMap = new HashMap<>(); + + static { + for (OpCode type : OpCode.values()) { + intToTypeMap[type.opcode & 0xFF] = type; + stringToByteMap.put(type.name(), type.opcode); + } + } + + //require = required args + //return = required return + private OpCode(int op, int require, int ret, Tier tier, CallFlags ... callFlags) { + this.opcode = (byte) op; + this.require = require; + this.tier = tier; + this.ret = ret; + this.callFlags = callFlags.length == 0 ? EnumSet.noneOf(CallFlags.class) : + EnumSet.copyOf(Arrays.asList(callFlags)); + } + + + public byte val() { + return opcode; + } + + /** + * Returns the mininum amount of items required on the stack for this operation + * + * @return minimum amount of expected items on the stack + */ + public int require() { + return require; + } + + public int ret() { + return ret; + } + + public int asInt() { + return opcode; + } + + public static boolean contains(String code) { + return stringToByteMap.containsKey(code.trim()); + } + + public static byte byteVal(String code) { + return stringToByteMap.get(code); + } + + public static OpCode code(byte code) { + return intToTypeMap[code & 0xFF]; + } + + private EnumSet getCallFlags() { + return callFlags; + } + + /** + * Indicates that opcode is a call + */ + public boolean isCall() { + return getCallFlags().contains(CallFlags.Call); + } + + private void checkCall() { + if (!isCall()) throw new RuntimeException("Opcode is not a call: " + this); + } + + /** + * Indicates that the code is executed in the context of the caller + */ + public boolean callIsStateless() { + checkCall(); + return getCallFlags().contains(CallFlags.Stateless); + } + + /** + * Indicates that the opcode has value parameter (3rd on stack) + */ + public boolean callHasValue() { + checkCall(); + return getCallFlags().contains(CallFlags.HasValue); + } + + /** + * Indicates that any state modifications are disallowed during the call + */ + public boolean callIsStatic() { + checkCall(); + return getCallFlags().contains(CallFlags.Static); + } + + /** + * Indicates that value and message sender are propagated from parent to child scope + */ + public boolean callIsDelegate() { + checkCall(); + return getCallFlags().contains(CallFlags.Delegate); + } + + public Tier getTier() { + return this.tier; + } + + public enum Tier { + ZeroTier(0), + BaseTier(2), + VeryLowTier(3), + LowTier(5), + MidTier(8), + HighTier(10), + ExtTier(20), + SpecialTier(1), //TODO #POC9 is this correct?? "multiparam" from cpp + InvalidTier(0); + + + private final int level; + + private Tier(int level) { + this.level = level; + } + + public int asInt() { + return level; + } + } + + private enum CallFlags { + /** + * Indicates that opcode is a call + */ + Call, + + /** + * Indicates that the code is executed in the context of the caller + */ + Stateless, + + /** + * Indicates that the opcode has value parameter (3rd on stack) + */ + HasValue, + + /** + * Indicates that any state modifications are disallowed during the call + */ + Static, + + /** + * Indicates that value and message sender are propagated from parent to child scope + */ + Delegate + } +} + + diff --git a/core/src/main/java/org/platon/core/vm/PrecompiledContracts.java b/core/src/main/java/org/platon/core/vm/PrecompiledContracts.java new file mode 100644 index 0000000..ef6c300 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/PrecompiledContracts.java @@ -0,0 +1,484 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +import org.apache.commons.lang3.tuple.Pair; +import org.platon.common.utils.BIUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.config.BlockchainConfig; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.platon.crypto.WalletUtil; +import org.platon.crypto.zksnark.*; + +import java.math.BigInteger; + +import static org.platon.common.utils.BIUtil.*; +import static org.platon.common.utils.ByteUtil.*; + +/** + * @author Roman Mandeleil + * @since 09.01.2015 + */ +public class PrecompiledContracts { + + private static final ECRecover ecRecover = new ECRecover(); + private static final Sha256 sha256 = new Sha256(); + private static final Ripempd160 ripempd160 = new Ripempd160(); + private static final Identity identity = new Identity(); + private static final ModExp modExp = new ModExp(); + private static final BN128Addition altBN128Add = new BN128Addition(); + private static final BN128Multiplication altBN128Mul = new BN128Multiplication(); + private static final BN128Pairing altBN128Pairing = new BN128Pairing(); + + private static final DataWord ecRecoverAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000001"); + private static final DataWord sha256Addr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000002"); + private static final DataWord ripempd160Addr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000003"); + private static final DataWord identityAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000004"); + private static final DataWord modExpAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000005"); + private static final DataWord altBN128AddAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000006"); + private static final DataWord altBN128MulAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000007"); + private static final DataWord altBN128PairingAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000008"); + + public static PrecompiledContract getContractForAddress(DataWord address, BlockchainConfig config) { + + if (address == null) return identity; + if (address.equals(ecRecoverAddr)) return ecRecover; + if (address.equals(sha256Addr)) return sha256; + if (address.equals(ripempd160Addr)) return ripempd160; + if (address.equals(identityAddr)) return identity; + + return null; + } + + private static byte[] encodeRes(byte[] w1, byte[] w2) { + + byte[] res = new byte[64]; + + w1 = stripLeadingZeroes(w1); + w2 = stripLeadingZeroes(w2); + + System.arraycopy(w1, 0, res, 32 - w1.length, w1.length); + System.arraycopy(w2, 0, res, 64 - w2.length, w2.length); + + return res; + } + + public static abstract class PrecompiledContract { + public abstract long getEnergonForData(byte[] data); + + public abstract Pair execute(byte[] data); + } + + public static class Identity extends PrecompiledContract { + + public Identity() { + } + + @Override + public long getEnergonForData(byte[] data) { + + // Energon charge for the execution: + // minimum 1 and additional 1 for each 32 bytes word (round up) + if (data == null) return 15; + return 15 + (data.length + 31) / 32 * 3; + } + + @Override + public Pair execute(byte[] data) { + return Pair.of(true, data); + } + } + + public static class Sha256 extends PrecompiledContract { + + + @Override + public long getEnergonForData(byte[] data) { + + // Energon charge for the execution: + // minimum 50 and additional 50 for each 32 bytes word (round up) + if (data == null) return 60; + return 60 + (data.length + 31) / 32 * 12; + } + + @Override + public Pair execute(byte[] data) { + + if (data == null) return Pair.of(true, HashUtil.sha3(EMPTY_BYTE_ARRAY)); + return Pair.of(true, HashUtil.sha3(data)); + } + } + + + public static class Ripempd160 extends PrecompiledContract { + + + @Override + public long getEnergonForData(byte[] data) { + + // TODO #POC9 Replace magic numbers with constants + // Energon charge for the execution: + // minimum 50 and additional 50 for each 32 bytes word (round up) + if (data == null) return 600; + return 600 + (data.length + 31) / 32 * 120; + } + + @Override + public Pair execute(byte[] data) { + + byte[] result = null; + if (data == null) result = HashUtil.ripemd160(EMPTY_BYTE_ARRAY); + else result = HashUtil.ripemd160(data); + + return Pair.of(true, DataWord.of(result).getData()); + } + } + + + public static class ECRecover extends PrecompiledContract { + + @Override + public long getEnergonForData(byte[] data) { + return 3000; + } + + @Override + public Pair execute(byte[] data) { + + byte[] h = new byte[32]; + byte[] v = new byte[32]; + byte[] r = new byte[32]; + byte[] s = new byte[32]; + + DataWord out = null; + + try { + System.arraycopy(data, 0, h, 0, 32); + System.arraycopy(data, 32, v, 0, 32); + System.arraycopy(data, 64, r, 0, 32); + + int sLength = data.length < 128 ? data.length - 96 : 32; + System.arraycopy(data, 96, s, 0, sLength); + + ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v[31]); + if (validateV(v) && signature.validateComponents()) { + out = DataWord.of(WalletUtil.signatureToAddress(h, signature)); + } + } catch (Throwable any) { + } + + if (out == null) { + return Pair.of(true, EMPTY_BYTE_ARRAY); + } else { + return Pair.of(true, out.getData()); + } + } + + private static boolean validateV(byte[] v) { + for (int i = 0; i < v.length - 1; i++) { + if (v[i] != 0) return false; + } + return true; + } + } + + /** + * Computes modular exponentiation on big numbers + * + * format of data[] array: + * [length_of_BASE] [length_of_EXPONENT] [length_of_MODULUS] [BASE] [EXPONENT] [MODULUS] + * where every length is a 32-byte left-padded integer representing the number of bytes. + * Call data is assumed to be infinitely right-padded with zero bytes. + * + * Returns an output as a byte array with the same length as the modulus + */ + public static class ModExp extends PrecompiledContract { + + private static final BigInteger GQUAD_DIVISOR = BigInteger.valueOf(20); + + private static final int ARGS_OFFSET = 32 * 3; // addresses length part + + @Override + public long getEnergonForData(byte[] data) { + + if (data == null) data = EMPTY_BYTE_ARRAY; + + int baseLen = parseLen(data, 0); + int expLen = parseLen(data, 1); + int modLen = parseLen(data, 2); + + byte[] expHighBytes = parseBytes(data, addSafely(ARGS_OFFSET, baseLen), Math.min(expLen, 32)); + + long multComplexity = getMultComplexity(Math.max(baseLen, modLen)); + long adjExpLen = getAdjustedExponentLength(expHighBytes, expLen); + + // use big numbers to stay safe in case of overflow + BigInteger energon = BigInteger.valueOf(multComplexity) + .multiply(BigInteger.valueOf(Math.max(adjExpLen, 1))) + .divide(GQUAD_DIVISOR); + + return isLessThan(energon, BigInteger.valueOf(Long.MAX_VALUE)) ? energon.longValue() : Long.MAX_VALUE; + } + + @Override + public Pair execute(byte[] data) { + + if (data == null) + return Pair.of(true, EMPTY_BYTE_ARRAY); + + int baseLen = parseLen(data, 0); + int expLen = parseLen(data, 1); + int modLen = parseLen(data, 2); + + BigInteger base = parseArg(data, ARGS_OFFSET, baseLen); + BigInteger exp = parseArg(data, addSafely(ARGS_OFFSET, baseLen), expLen); + BigInteger mod = parseArg(data, addSafely(addSafely(ARGS_OFFSET, baseLen), expLen), modLen); + + // check if modulus is zero + if (isZero(mod)) + return Pair.of(true, EMPTY_BYTE_ARRAY); + + byte[] res = stripLeadingZeroes(base.modPow(exp, mod).toByteArray()); + + // adjust result to the same length as the modulus has + if (res.length < modLen) { + + byte[] adjRes = new byte[modLen]; + System.arraycopy(res, 0, adjRes, modLen - res.length, res.length); + + return Pair.of(true, adjRes); + + } else { + return Pair.of(true, res); + } + } + + private long getMultComplexity(long x) { + + long x2 = x * x; + + if (x <= 64) return x2; + if (x <= 1024) return x2 / 4 + 96 * x - 3072; + + return x2 / 16 + 480 * x - 199680; + } + + private long getAdjustedExponentLength(byte[] expHighBytes, long expLen) { + + int leadingZeros = numberOfLeadingZeros(expHighBytes); + int highestBit = 8 * expHighBytes.length - leadingZeros; + + // set index basement to zero + if (highestBit > 0) highestBit--; + + if (expLen <= 32) { + return highestBit; + } else { + return 8 * (expLen - 32) + highestBit; + } + } + + private int parseLen(byte[] data, int idx) { + byte[] bytes = parseBytes(data, 32 * idx, 32); + return DataWord.of(bytes).intValueSafe(); + } + + private BigInteger parseArg(byte[] data, int offset, int len) { + byte[] bytes = parseBytes(data, offset, len); + return bytesToBigInteger(bytes); + } + } + + /** + * Computes point addition on Barreto–Naehrig curve. + * See {@link BN128Fp} for details
+ *
+ * + * input data[]:
+ * two points encoded as (x, y), where x and y are 32-byte left-padded integers,
+ * if input is shorter than expected, it's assumed to be right-padded with zero bytes
+ *
+ * + * output:
+ * resulting point (x', y'), where x and y encoded as 32-byte left-padded integers
+ * + */ + public static class BN128Addition extends PrecompiledContract { + + @Override + public long getEnergonForData(byte[] data) { + return 500; + } + + @Override + public Pair execute(byte[] data) { + + if (data == null) + data = EMPTY_BYTE_ARRAY; + + byte[] x1 = parseWord(data, 0); + byte[] y1 = parseWord(data, 1); + + byte[] x2 = parseWord(data, 2); + byte[] y2 = parseWord(data, 3); + + BN128 p1 = BN128Fp.create(x1, y1); + if (p1 == null) + return Pair.of(false, EMPTY_BYTE_ARRAY); + + BN128 p2 = BN128Fp.create(x2, y2); + if (p2 == null) + return Pair.of(false, EMPTY_BYTE_ARRAY); + + BN128 res = p1.add(p2).toEthNotation(); + + return Pair.of(true, encodeRes(res.x().bytes(), res.y().bytes())); + } + } + + /** + * Computes multiplication of scalar value on a point belonging to Barreto–Naehrig curve. + * See {@link BN128Fp} for details
+ *
+ * + * input data[]:
+ * point encoded as (x, y) is followed by scalar s, where x, y and s are 32-byte left-padded integers,
+ * if input is shorter than expected, it's assumed to be right-padded with zero bytes
+ *
+ * + * output:
+ * resulting point (x', y'), where x and y encoded as 32-byte left-padded integers
+ * + */ + public static class BN128Multiplication extends PrecompiledContract { + + @Override + public long getEnergonForData(byte[] data) { + return 40000; + } + + @Override + public Pair execute(byte[] data) { + + if (data == null) + data = EMPTY_BYTE_ARRAY; + + byte[] x = parseWord(data, 0); + byte[] y = parseWord(data, 1); + + byte[] s = parseWord(data, 2); + + BN128 p = BN128Fp.create(x, y); + if (p == null) + return Pair.of(false, EMPTY_BYTE_ARRAY); + + BN128 res = p.mul(BIUtil.toBI(s)).toEthNotation(); + + return Pair.of(true, encodeRes(res.x().bytes(), res.y().bytes())); + } + } + + /** + * Computes pairing check.
+ * See {@link PairingCheck} for details.
+ *
+ * + * Input data[]:
+ * an array of points (a1, b1, ... , ak, bk),
+ * where "ai" is a point of {@link BN128Fp} curve and encoded as two 32-byte left-padded integers (x; y)
+ * "bi" is a point of {@link BN128G2} curve and encoded as four 32-byte left-padded integers {@code (ai + b; ci + d)}, + * each coordinate of the point is a big-endian {@link Fp2} number, so {@code b} precedes {@code a} in the encoding: + * {@code (b, a; d, c)}
+ * thus each pair (ai, bi) has 192 bytes length, if 192 is not a multiple of {@code data.length} then execution fails
+ * the number of pairs is derived from input length by dividing it by 192 (the length of a pair)
+ *
+ * + * output:
+ * pairing product which is either 0 or 1, encoded as 32-byte left-padded integer
+ * + */ + public static class BN128Pairing extends PrecompiledContract { + + private static final int PAIR_SIZE = 192; + + @Override + public long getEnergonForData(byte[] data) { + + if (data == null) return 100000; + + return 80000 * (data.length / PAIR_SIZE) + 100000; + } + + @Override + public Pair execute(byte[] data) { + + if (data == null) + data = EMPTY_BYTE_ARRAY; + + // fail if input len is not a multiple of PAIR_SIZE + if (data.length % PAIR_SIZE > 0) + return Pair.of(false, EMPTY_BYTE_ARRAY); + + PairingCheck check = PairingCheck.create(); + + // iterating over all pairs + for (int offset = 0; offset < data.length; offset += PAIR_SIZE) { + + Pair pair = decodePair(data, offset); + + // fail if decoding has failed + if (pair == null) + return Pair.of(false, EMPTY_BYTE_ARRAY); + + check.addPair(pair.getLeft(), pair.getRight()); + } + + check.run(); + int result = check.result(); + + return Pair.of(true, DataWord.of(result).getData()); + } + + private Pair decodePair(byte[] in, int offset) { + + byte[] x = parseWord(in, offset, 0); + byte[] y = parseWord(in, offset, 1); + + BN128G1 p1 = BN128G1.create(x, y); + + // fail if point is invalid + if (p1 == null) return null; + + // (b, a) + byte[] b = parseWord(in, offset, 2); + byte[] a = parseWord(in, offset, 3); + + // (d, c) + byte[] d = parseWord(in, offset, 4); + byte[] c = parseWord(in, offset, 5); + + BN128G2 p2 = BN128G2.create(a, b, c, d); + + // fail if point is invalid + if (p2 == null) return null; + + return Pair.of(p1, p2); + } + } +} diff --git a/core/src/main/java/org/platon/core/vm/VM.java b/core/src/main/java/org/platon/core/vm/VM.java new file mode 100644 index 0000000..608a581 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/VM.java @@ -0,0 +1,1422 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.config.BlockchainConfig; +import org.platon.core.config.CoreConfig; +import org.platon.core.db.ContractDetails; +import org.platon.core.transaction.LogInfo; +import org.platon.core.transaction.util.ByteUtil; +import org.platon.core.vm.program.Program; +import org.platon.core.vm.program.Stack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.math.BigInteger; +import java.util.*; +import java.util.function.Function; + +import static org.bouncycastle.util.encoders.Hex.toHexString; +import static org.platon.core.vm.OpCode.*; +import static org.platon.crypto.HashUtil.sha3; + + +/** + * The Ethereum Virtual Machine (EVM) is responsible for initialization + * and executing a transaction on a contract. + * + * It is a quasi-Turing-complete machine; the quasi qualification + * comes from the fact that the computation is intrinsically bounded + * through a parameter, Energon, which limits the total amount of computation done. + * + * The EVM is a simple stack-based architecture. The word size of the machine + * (and thus size of stack item) is 256-bit. This was chosen to facilitate + * the SHA3-256 hash scheme and elliptic-curve computations. The memory model + * is a simple word-addressed byte array. The stack has an unlimited size. + * The machine also has an independent storage model; this is similar in concept + * to the memory but rather than a byte array, it is a word-addressable word array. + * + * Unlike memory, which is volatile, storage is non volatile and is + * maintained as part of the system state. All locations in both storage + * and memory are well-defined initially as zero. + * + * The machine does not follow the standard von Neumann architecture. + * Rather than storing program code in generally-accessible memory or storage, + * it is stored separately in a virtual ROM interactable only though + * a specialised instruction. + * + * The machine can have exceptional execution for several reasons, + * including stack underflows and invalid instructions. These unambiguously + * and validly result in immediate halting of the machine with all state changes + * left intact. The one piece of exceptional execution that does not leave + * state changes intact is the out-of-Energon (OOG) exception. + * + * Here, the machine halts immediately and reports the issue to + * the execution agent (either the transaction processor or, recursively, + * the spawning execution environment) and which will deal with it separately. + * + * @author Roman Mandeleil + * @since 01.06.2014 + */ +public class VM { + + private static final Logger logger = LoggerFactory.getLogger("VM"); + private static final Logger dumpLogger = LoggerFactory.getLogger("dump"); + private static BigInteger _32_ = BigInteger.valueOf(32); + private static final String logString = "{} Op: [{}] Energon: [{}] Deep: [{}] Hint: [{}]"; + + // max mem size which couldn't be paid for ever + // used to reduce expensive BigInt arithmetic + private static BigInteger MAX_MEM_SIZE = BigInteger.valueOf(Integer.MAX_VALUE); + + + /* Keeps track of the number of steps performed in this VM */ + private int vmCounter = 0; + + private boolean vmTrace; + private long dumpBlock; + + private static final Map> opValidators = new HashMap>() + {{ + put(DELEGATECALL, (config) -> config.getConstants().hasDelegateCallOpcode()); +// put(REVERT, BlockchainConfig::eip206); +// put(RETURNDATACOPY, BlockchainConfig::eip211); +// put(RETURNDATASIZE, BlockchainConfig::eip211); +// put(STATICCALL, BlockchainConfig::eip214); +// put(EXTCODEHASH, BlockchainConfig::eip1052); +// put(SHL, BlockchainConfig::eip145); +// put(SHR, BlockchainConfig::eip145); +// put(SAR, BlockchainConfig::eip145); + }}; + + private final CoreConfig config; + + public VM() { + this(CoreConfig.getInstance()); + } + + @Autowired + public VM(CoreConfig config) { + this.config = config; + vmTrace = config.vmTrace(); + dumpBlock = config.dumpBlock(); + } + + private long calcMemEnergon(EnergonCost energonCosts, long oldMemSize, BigInteger newMemSize, long copySize) { + long EnergonCost = 0; + + // Avoid overflows + if (newMemSize.compareTo(MAX_MEM_SIZE) > 0) { + throw Program.Exception.EnergonOverflow(newMemSize, MAX_MEM_SIZE); + } + + // memory Energon calc + long memoryUsage = (newMemSize.longValue() + 31) / 32 * 32; + if (memoryUsage > oldMemSize) { + long memWords = (memoryUsage / 32); + long memWordsOld = (oldMemSize / 32); + //TODO #POC9 c_quadCoeffDiv = 512, this should be a constant, not magic number + long memEnergon = (energonCosts.getMEMORY() * memWords + memWords * memWords / 512) + - (energonCosts.getMEMORY() * memWordsOld + memWordsOld * memWordsOld / 512); + EnergonCost += memEnergon; + } + + if (copySize > 0) { + long copyEnergon = energonCosts.getCOPY_ENERGON() * ((copySize + 31) / 32); + EnergonCost += copyEnergon; + } + return EnergonCost; + } + + private boolean isDeadAccount(Program program, byte[] addr) { + return !program.getStorage().isExist(addr) || program.getStorage().getAccount(addr).isEmpty(); + } + + /** + * Validates whether operation is allowed + * with current blockchain config + * @param op VM operation + * @param program Current program + */ + private void validateOp(OpCode op, Program program) { + if (!(opValidators.containsKey(op))) return; + + BlockchainConfig blockchainConfig = program.getBlockchainConfig(); + if (!opValidators.get(op).apply(blockchainConfig)) { + throw Program.Exception.invalidOpCode(program.getCurrentOp()); + } + } + + public void step(Program program) { + + if (vmTrace) { + program.saveOpTrace(); + } + + try { + BlockchainConfig blockchainConfig = program.getBlockchainConfig(); + + OpCode op = OpCode.code(program.getCurrentOp()); + if (op == null) { + throw Program.Exception.invalidOpCode(program.getCurrentOp()); + } + + validateOp(op, program); + + program.setLastOp(op.val()); + program.verifyStackSize(op.require()); + program.verifyStackOverflow(op.require(), op.ret()); //Check not exceeding stack limits + + long oldMemSize = program.getMemSize(); + Stack stack = program.getStack(); + + String hint = ""; + long callEnergon = 0, memWords = 0; // parameters for logging + long energonCost = op.getTier().asInt(); + long energonBefore = program.getEnergonLong(); + int stepBefore = program.getPC(); + EnergonCost energonCosts = blockchainConfig.getEnergonCost(); + DataWord adjustedCallEnergon = null; + + /*DEBUG #POC9 if( op.asInt() == 96 || op.asInt() == -128 || op.asInt() == 57 || op.asInt() == 115) { + //byte alphaone = 0x63; + //op = OpCode.code(alphaone); + energonCost = 3; + } + + if( op.asInt() == -13 ) { + //byte alphaone = 0x63; + //op = OpCode.code(alphaone); + energonCost = 0; + }*/ + + // Calculate fees and spend Energon + switch (op) { + case STOP: + energonCost = energonCosts.getSTOP(); + break; + case SUICIDE: + energonCost = energonCosts.getSUICIDE(); + DataWord suicideAddressWord = stack.get(stack.size() - 1); + if (!program.getStorage().isExist(suicideAddressWord.getLast20Bytes())) { + energonCost += energonCosts.getNEW_ACCT_SUICIDE(); + } + break; + case SSTORE: + DataWord newValue = stack.get(stack.size() - 2); + DataWord oldValue = program.storageLoad(stack.peek()); + if (oldValue == null && !newValue.isZero()) + energonCost = energonCosts.getSET_SSTORE(); + else if (oldValue != null && newValue.isZero()) { + // todo: EnergonREFUND counter policy + + // refund step cost policy. + program.futureRefundEnergon(energonCosts.getREFUND_SSTORE()); + energonCost = energonCosts.getCLEAR_SSTORE(); + } else + energonCost = energonCosts.getRESET_SSTORE(); + break; + case SLOAD: + energonCost = energonCosts.getSLOAD(); + break; + case BALANCE: + energonCost = energonCosts.getBALANCE(); + break; + + // These all operate on memory and therefore potentially expand it: + case MSTORE: + energonCost += calcMemEnergon(energonCosts, oldMemSize, memNeeded(stack.peek(), DataWord.of(32)), 0); + break; + case MSTORE8: + energonCost += calcMemEnergon(energonCosts, oldMemSize, memNeeded(stack.peek(), DataWord.ONE), 0); + break; + case MLOAD: + energonCost += calcMemEnergon(energonCosts, oldMemSize, memNeeded(stack.peek(), DataWord.of(32)), 0); + break; + case RETURN: + case REVERT: + energonCost = energonCosts.getSTOP() + calcMemEnergon(energonCosts, oldMemSize, + memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); + break; + case SHA3: + energonCost = energonCosts.getSHA3() + calcMemEnergon(energonCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); + DataWord size = stack.get(stack.size() - 2); + long chunkUsed = (size.longValueSafe() + 31) / 32; + energonCost += chunkUsed * energonCosts.getSHA3_WORD(); + break; + case CALLDATACOPY: + case RETURNDATACOPY: + energonCost += calcMemEnergon(energonCosts, oldMemSize, + memNeeded(stack.peek(), stack.get(stack.size() - 3)), + stack.get(stack.size() - 3).longValueSafe()); + break; + case CODECOPY: + energonCost += calcMemEnergon(energonCosts, oldMemSize, + memNeeded(stack.peek(), stack.get(stack.size() - 3)), + stack.get(stack.size() - 3).longValueSafe()); + break; + case EXTCODESIZE: + energonCost = energonCosts.getEXT_CODE_SIZE(); + break; + case EXTCODECOPY: + energonCost = energonCosts.getEXT_CODE_COPY() + calcMemEnergon(energonCosts, oldMemSize, + memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 4)), + stack.get(stack.size() - 4).longValueSafe()); + break; + case EXTCODEHASH: + energonCost = energonCosts.getEXT_CODE_HASH(); + break; + case CALL: + case CALLCODE: + case DELEGATECALL: + case STATICCALL: + + energonCost = energonCosts.getCALL(); + DataWord callEnergonWord = stack.get(stack.size() - 1); + + DataWord callAddressWord = stack.get(stack.size() - 2); + + DataWord value = op.callHasValue() ? + stack.get(stack.size() - 3) : DataWord.ZERO; + + //check to see if account does not exist and is not a precompiled contract + if (op == CALL) { + if (!program.getStorage().isExist(callAddressWord.getLast20Bytes())) { + energonCost += energonCosts.getNEW_ACCT_CALL(); + } + } + + //TODO #POC9 Make sure this is converted to BigInteger (256num support) + if (!value.isZero() ) + energonCost += energonCosts.getVT_CALL(); + + int opOff = op.callHasValue() ? 4 : 3; + BigInteger in = memNeeded(stack.get(stack.size() - opOff), stack.get(stack.size() - opOff - 1)); // in offset+size + BigInteger out = memNeeded(stack.get(stack.size() - opOff - 2), stack.get(stack.size() - opOff - 3)); // out offset+size + energonCost += calcMemEnergon(energonCosts, oldMemSize, in.max(out), 0); + + if (energonCost > program.getEnergon().longValueSafe()) { + throw Program.Exception.notEnoughOpEnergon(op, callEnergonWord, program.getEnergon()); + } + + DataWord EnergonLeft = program.getEnergon(); + DataWord subResult = EnergonLeft.sub(DataWord.of(energonCost)); + adjustedCallEnergon = blockchainConfig.getCallEnergon(op, callEnergonWord, subResult); + energonCost += adjustedCallEnergon.longValueSafe(); + break; + case CREATE: + energonCost = energonCosts.getCREATE() + calcMemEnergon(energonCosts, oldMemSize, + memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0); + break; + case LOG0: + case LOG1: + case LOG2: + case LOG3: + case LOG4: + + int nTopics = op.val() - OpCode.LOG0.val(); + + BigInteger dataSize = stack.get(stack.size() - 2).value(); + BigInteger dataCost = dataSize.multiply(BigInteger.valueOf(energonCosts.getLOG_DATA_ENERGON())); + if (program.getEnergon().value().compareTo(dataCost) < 0) { + throw Program.Exception.notEnoughOpEnergon(op, dataCost, program.getEnergon().value()); + } + + energonCost = energonCosts.getLOG_ENERGON() + + energonCosts.getLOG_TOPIC_ENERGON() * nTopics + + energonCosts.getLOG_DATA_ENERGON() * stack.get(stack.size() - 2).longValue() + + calcMemEnergon(energonCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); + break; + case EXP: + + DataWord exp = stack.get(stack.size() - 2); + int bytesOccupied = exp.bytesOccupied(); + energonCost = energonCosts.getEXP_ENERGON() + energonCosts.getEXP_BYTE_ENERGON() * bytesOccupied; + break; + default: + break; + } + + //DEBUG System.out.println(" OP IS " + op.name() + " EnergonCOST IS " + EnergonCost + " NUM IS " + op.asInt()); + program.spendEnergon(energonCost, op.name()); + + // Log debugging line for VM + if (program.getNumber().intValue() == dumpBlock) + this.dumpLine(op, energonBefore, energonCost + callEnergon, memWords, program); + + // Execute operation + switch (op) { + /** + * Stop and Arithmetic Operations + */ + case STOP: { + program.setHReturn(ByteUtil.EMPTY_BYTE_ARRAY); + program.stop(); + } + break; + case ADD: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " + " + word2.value(); + + DataWord addResult = word1.add(word2); + program.stackPush(addResult); + program.step(); + + } + break; + case MUL: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " * " + word2.value(); + + DataWord mulResult = word1.mul(word2); + program.stackPush(mulResult); + program.step(); + } + break; + case SUB: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " - " + word2.value(); + + DataWord subResult = word1.sub(word2); + program.stackPush(subResult); + program.step(); + } + break; + case DIV: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " / " + word2.value(); + + DataWord divResult = word1.div(word2); + program.stackPush(divResult); + program.step(); + } + break; + case SDIV: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.sValue() + " / " + word2.sValue(); + + DataWord sDivResult = word1.sDiv(word2); + program.stackPush(sDivResult); + program.step(); + } + break; + case MOD: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " % " + word2.value(); + + DataWord modResult = word1.mod(word2); + program.stackPush(modResult); + program.step(); + } + break; + case SMOD: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.sValue() + " #% " + word2.sValue(); + + DataWord sModResult = word1.sMod(word2); + program.stackPush(sModResult); + program.step(); + } + break; + case EXP: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " ** " + word2.value(); + + DataWord expResult = word1.exp(word2); + program.stackPush(expResult); + program.step(); + } + break; + case SIGNEXTEND: { + DataWord word1 = program.stackPop(); + BigInteger k = word1.value(); + + if (k.compareTo(_32_) < 0) { + DataWord word2 = program.stackPop(); + if (logger.isInfoEnabled()) + hint = word1 + " " + word2.value(); + DataWord extendResult = word2.signExtend(k.byteValue()); + program.stackPush(extendResult); + } + program.step(); + } + break; + case NOT: { + DataWord word1 = program.stackPop(); + DataWord bnotWord = word1.bnot(); + + if (logger.isInfoEnabled()) + hint = "" + bnotWord.value(); + + program.stackPush(bnotWord); + program.step(); + } + break; + case LT: { + // TODO: can be improved by not using BigInteger + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " < " + word2.value(); + + if (word1.value().compareTo(word2.value()) == -1) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + program.step(); + } + break; + case SLT: { + // TODO: can be improved by not using BigInteger + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.sValue() + " < " + word2.sValue(); + + if (word1.sValue().compareTo(word2.sValue()) == -1) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + program.step(); + } + break; + case SGT: { + // TODO: can be improved by not using BigInteger + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.sValue() + " > " + word2.sValue(); + + if (word1.sValue().compareTo(word2.sValue()) == 1) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + program.step(); + } + break; + case GT: { + // TODO: can be improved by not using BigInteger + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " > " + word2.value(); + + if (word1.value().compareTo(word2.value()) == 1) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + program.step(); + } + break; + case EQ: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " == " + word2.value(); + + DataWord xorResult = word1.xor(word2); + if (xorResult.isZero()) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + program.step(); + } + break; + case ISZERO: { + DataWord word1 = program.stackPop(); + if (word1.isZero()) { + program.stackPush(DataWord.ONE); + } else { + program.stackPush(DataWord.ZERO); + } + + if (logger.isInfoEnabled()) + hint = "" + word1.value(); + + program.step(); + } + break; + + /** + * Bitwise Logic Operations + */ + case AND: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " && " + word2.value(); + + DataWord andResult = word1.and(word2); + program.stackPush(andResult); + program.step(); + } + break; + case OR: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " || " + word2.value(); + + DataWord orResult = word1.or(word2); + program.stackPush(orResult); + program.step(); + } + break; + case XOR: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = word1.value() + " ^ " + word2.value(); + + DataWord xorResult = word1.xor(word2); + program.stackPush(xorResult); + program.step(); + } + break; + case BYTE: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + final DataWord result; + if (word1.value().compareTo(_32_) == -1) { + byte tmp = word2.getData()[word1.intValue()]; + result = DataWord.of(tmp); + } else { + result = DataWord.ZERO; + } + + if (logger.isInfoEnabled()) + hint = "" + result.value(); + + program.stackPush(result); + program.step(); + } + break; + case SHL: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + final DataWord result = word2.shiftLeft(word1); + + if (logger.isInfoEnabled()) + hint = "" + result.value(); + + program.stackPush(result); + program.step(); + } + break; + case SHR: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + final DataWord result = word2.shiftRight(word1); + + if (logger.isInfoEnabled()) + hint = "" + result.value(); + + program.stackPush(result); + program.step(); + } + break; + case SAR: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + final DataWord result = word2.shiftRightSigned(word1); + + if (logger.isInfoEnabled()) + hint = "" + result.value(); + + program.stackPush(result); + program.step(); + } + break; + case ADDMOD: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + DataWord word3 = program.stackPop(); + DataWord addmodResult = word1.addmod(word2, word3); + program.stackPush(addmodResult); + program.step(); + } + break; + case MULMOD: { + DataWord word1 = program.stackPop(); + DataWord word2 = program.stackPop(); + DataWord word3 = program.stackPop(); + DataWord mulmodResult = word1.mulmod(word2, word3); + program.stackPush(mulmodResult); + program.step(); + } + break; + + /** + * SHA3 + */ + case SHA3: { + DataWord memOffsetData = program.stackPop(); + DataWord lengthData = program.stackPop(); + byte[] buffer = program.memoryChunk(memOffsetData.intValueSafe(), lengthData.intValueSafe()); + + byte[] encoded = sha3(buffer); + DataWord word = DataWord.of(encoded); + + if (logger.isInfoEnabled()) + hint = word.toString(); + + program.stackPush(word); + program.step(); + } + break; + + /** + * Environmental Information + */ + case ADDRESS: { + DataWord address = program.getOwnerAddress(); + + if (logger.isInfoEnabled()) + hint = "address: " + toHexString(address.getLast20Bytes()); + + program.stackPush(address); + program.step(); + } + break; + case BALANCE: { + DataWord address = program.stackPop(); + DataWord balance = program.getBalance(address); + + if (logger.isInfoEnabled()) + hint = "address: " + + toHexString(address.getLast20Bytes()) + + " balance: " + balance.toString(); + + program.stackPush(balance); + program.step(); + } + break; + case ORIGIN: { + DataWord originAddress = program.getOriginAddress(); + + if (logger.isInfoEnabled()) + hint = "address: " + toHexString(originAddress.getLast20Bytes()); + + program.stackPush(originAddress); + program.step(); + } + break; + case CALLER: { + DataWord callerAddress = program.getCallerAddress(); + + if (logger.isInfoEnabled()) + hint = "address: " + toHexString(callerAddress.getLast20Bytes()); + + program.stackPush(callerAddress); + program.step(); + } + break; + case CALLVALUE: { + DataWord callValue = program.getCallValue(); + + if (logger.isInfoEnabled()) + hint = "value: " + callValue; + + program.stackPush(callValue); + program.step(); + } + break; + case CALLDATALOAD: { + DataWord dataOffs = program.stackPop(); + DataWord value = program.getDataValue(dataOffs); + + if (logger.isInfoEnabled()) + hint = "data: " + value; + + program.stackPush(value); + program.step(); + } + break; + case CALLDATASIZE: { + DataWord dataSize = program.getDataSize(); + + if (logger.isInfoEnabled()) + hint = "size: " + dataSize.value(); + + program.stackPush(dataSize); + program.step(); + } + break; + case CALLDATACOPY: { + DataWord memOffsetData = program.stackPop(); + DataWord dataOffsetData = program.stackPop(); + DataWord lengthData = program.stackPop(); + + byte[] msgData = program.getDataCopy(dataOffsetData, lengthData); + + if (logger.isInfoEnabled()) + hint = "data: " + toHexString(msgData); + + program.memorySave(memOffsetData.intValueSafe(), lengthData.intValueSafe(), msgData); + program.step(); + } + break; + case RETURNDATASIZE: { + DataWord dataSize = program.getReturnDataBufferSize(); + + if (logger.isInfoEnabled()) + hint = "size: " + dataSize.value(); + + program.stackPush(dataSize); + program.step(); + } + break; + case RETURNDATACOPY: { + DataWord memOffsetData = program.stackPop(); + DataWord dataOffsetData = program.stackPop(); + DataWord lengthData = program.stackPop(); + + byte[] msgData = program.getReturnDataBufferData(dataOffsetData, lengthData); + + if (msgData == null) { + throw new Program.ReturnDataCopyIllegalBoundsException(dataOffsetData, lengthData, program.getReturnDataBufferSize().longValueSafe()); + } + + if (logger.isInfoEnabled()) + hint = "data: " + toHexString(msgData); + + program.memorySave(memOffsetData.intValueSafe(), lengthData.intValueSafe(), msgData); + program.step(); + } + break; + case CODESIZE: + case EXTCODESIZE: { + + int length; + if (op == OpCode.CODESIZE) + length = program.getCode().length; + else { + DataWord address = program.stackPop(); + length = program.getCodeAt(address).length; + } + DataWord codeLength = DataWord.of(length); + + if (logger.isInfoEnabled()) + hint = "size: " + length; + + program.stackPush(codeLength); + program.step(); + } + break; + case CODECOPY: + case EXTCODECOPY: { + + byte[] fullCode = ByteUtil.EMPTY_BYTE_ARRAY; + if (op == OpCode.CODECOPY) + fullCode = program.getCode(); + + if (op == OpCode.EXTCODECOPY) { + DataWord address = program.stackPop(); + fullCode = program.getCodeAt(address); + } + + int memOffset = program.stackPop().intValueSafe(); + int codeOffset = program.stackPop().intValueSafe(); + int lengthData = program.stackPop().intValueSafe(); + + int sizeToBeCopied = + (long) codeOffset + lengthData > fullCode.length ? + (fullCode.length < codeOffset ? 0 : fullCode.length - codeOffset) + : lengthData; + + byte[] codeCopy = new byte[lengthData]; + + if (codeOffset < fullCode.length) + System.arraycopy(fullCode, codeOffset, codeCopy, 0, sizeToBeCopied); + + if (logger.isInfoEnabled()) + hint = "code: " + toHexString(codeCopy); + + program.memorySave(memOffset, lengthData, codeCopy); + program.step(); + } + break; + case EXTCODEHASH: { + DataWord address = program.stackPop(); + byte[] codeHash = program.getCodeHashAt(address); + program.stackPush(codeHash); + program.step(); + } + break; + case GASPRICE: { + DataWord EnergonPrice = program.getEnergonPrice(); + + if (logger.isInfoEnabled()) + hint = "price: " + EnergonPrice.toString(); + + program.stackPush(EnergonPrice); + program.step(); + } + break; + + /** + * Block Information + */ + case BLOCKHASH: { + + int blockIndex = program.stackPop().intValueSafe(); + + DataWord blockHash = program.getBlockHash(blockIndex); + + if (logger.isInfoEnabled()) + hint = "blockHash: " + blockHash; + + program.stackPush(blockHash); + program.step(); + } + break; + case COINBASE: { + DataWord coinbase = program.getCoinbase(); + + if (logger.isInfoEnabled()) + hint = "coinbase: " + toHexString(coinbase.getLast20Bytes()); + + program.stackPush(coinbase); + program.step(); + } + break; + case TIMESTAMP: { + DataWord timestamp = program.getTimestamp(); + + if (logger.isInfoEnabled()) + hint = "timestamp: " + timestamp.value(); + + program.stackPush(timestamp); + program.step(); + } + break; + case NUMBER: { + DataWord number = program.getNumber(); + + if (logger.isInfoEnabled()) + hint = "number: " + number.value(); + + program.stackPush(number); + program.step(); + } + break; + case DIFFICULTY: { + DataWord difficulty = program.getDifficulty(); + + if (logger.isInfoEnabled()) + hint = "difficulty: " + difficulty; + + program.stackPush(difficulty); + program.step(); + } + break; + case GASLIMIT: { + DataWord energonlimit = program.getEnergonLimit(); + + if (logger.isInfoEnabled()) + hint = "energonlimit: " + energonlimit; + + program.stackPush(energonlimit); + program.step(); + } + break; + case POP: { + program.stackPop(); + program.step(); + } break; + case DUP1: case DUP2: case DUP3: case DUP4: + case DUP5: case DUP6: case DUP7: case DUP8: + case DUP9: case DUP10: case DUP11: case DUP12: + case DUP13: case DUP14: case DUP15: case DUP16:{ + + int n = op.val() - OpCode.DUP1.val() + 1; + DataWord word_1 = stack.get(stack.size() - n); + program.stackPush(word_1); + program.step(); + + } break; + case SWAP1: case SWAP2: case SWAP3: case SWAP4: + case SWAP5: case SWAP6: case SWAP7: case SWAP8: + case SWAP9: case SWAP10: case SWAP11: case SWAP12: + case SWAP13: case SWAP14: case SWAP15: case SWAP16:{ + + int n = op.val() - OpCode.SWAP1.val() + 2; + stack.swap(stack.size() - 1, stack.size() - n); + program.step(); + } + break; + case LOG0: + case LOG1: + case LOG2: + case LOG3: + case LOG4: { + + if (program.isStaticCall()) throw new Program.StaticCallModificationException(); + DataWord address = program.getOwnerAddress(); + + DataWord memStart = stack.pop(); + DataWord memOffset = stack.pop(); + + int nTopics = op.val() - OpCode.LOG0.val(); + + List topics = new ArrayList<>(); + for (int i = 0; i < nTopics; ++i) { + DataWord topic = stack.pop(); + topics.add(topic); + } + + byte[] data = program.memoryChunk(memStart.intValueSafe(), memOffset.intValueSafe()); + + LogInfo logInfo = new LogInfo(address.getLast20Bytes(), topics, data); + + if (logger.isInfoEnabled()) + hint = logInfo.toString(); + + program.getResult().addLogInfo(logInfo); + program.step(); + } + break; + case MLOAD: { + DataWord addr = program.stackPop(); + DataWord data = program.memoryLoad(addr); + + if (logger.isInfoEnabled()) + hint = "data: " + data; + + program.stackPush(data); + program.step(); + } + break; + case MSTORE: { + DataWord addr = program.stackPop(); + DataWord value = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = "addr: " + addr + " value: " + value; + + program.memorySave(addr, value); + program.step(); + } + break; + case MSTORE8: { + DataWord addr = program.stackPop(); + DataWord value = program.stackPop(); + byte[] byteVal = {value.getData()[31]}; + program.memorySave(addr.intValueSafe(), byteVal); + program.step(); + } + break; + case SLOAD: { + DataWord key = program.stackPop(); + DataWord val = program.storageLoad(key); + + if (logger.isInfoEnabled()) + hint = "key: " + key + " value: " + val; + + if (val == null) + val = key.and(DataWord.ZERO); + + program.stackPush(val); + program.step(); + } + break; + case SSTORE: { + if (program.isStaticCall()) throw new Program.StaticCallModificationException(); + + DataWord addr = program.stackPop(); + DataWord value = program.stackPop(); + + if (logger.isInfoEnabled()) + hint = "[" + program.getOwnerAddress().toPrefixString() + "] key: " + addr + " value: " + value; + + program.storageSave(addr, value); + program.step(); + } + break; + case JUMP: { + DataWord pos = program.stackPop(); + int nextPC = program.verifyJumpDest(pos); + + if (logger.isInfoEnabled()) + hint = "~> " + nextPC; + + program.setPC(nextPC); + + } + break; + case JUMPI: { + DataWord pos = program.stackPop(); + DataWord cond = program.stackPop(); + + if (!cond.isZero()) { + int nextPC = program.verifyJumpDest(pos); + + if (logger.isInfoEnabled()) + hint = "~> " + nextPC; + + program.setPC(nextPC); + } else { + program.step(); + } + + } + break; + case PC: { + int pc = program.getPC(); + DataWord pcWord = DataWord.of(pc); + + if (logger.isInfoEnabled()) + hint = pcWord.toString(); + + program.stackPush(pcWord); + program.step(); + } + break; + case MSIZE: { + int memSize = program.getMemSize(); + DataWord wordMemSize = DataWord.of(memSize); + + if (logger.isInfoEnabled()) + hint = "" + memSize; + + program.stackPush(wordMemSize); + program.step(); + } + break; + case GAS: { + DataWord energon = program.getEnergon(); + + if (logger.isInfoEnabled()) + hint = "" + energon; + + program.stackPush(energon); + program.step(); + } + break; + + case PUSH1: + case PUSH2: + case PUSH3: + case PUSH4: + case PUSH5: + case PUSH6: + case PUSH7: + case PUSH8: + case PUSH9: + case PUSH10: + case PUSH11: + case PUSH12: + case PUSH13: + case PUSH14: + case PUSH15: + case PUSH16: + case PUSH17: + case PUSH18: + case PUSH19: + case PUSH20: + case PUSH21: + case PUSH22: + case PUSH23: + case PUSH24: + case PUSH25: + case PUSH26: + case PUSH27: + case PUSH28: + case PUSH29: + case PUSH30: + case PUSH31: + case PUSH32: { + program.step(); + int nPush = op.val() - PUSH1.val() + 1; + + byte[] data = program.sweep(nPush); + + if (logger.isInfoEnabled()) + hint = "" + toHexString(data); + + program.stackPush(data); + } + break; + case JUMPDEST: { + program.step(); + } + break; + case CREATE: { + if (program.isStaticCall()) throw new Program.StaticCallModificationException(); + + DataWord value = program.stackPop(); + DataWord inOffset = program.stackPop(); + DataWord inSize = program.stackPop(); + + if (logger.isInfoEnabled()) + logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), + String.format("%-12s", op.name()), + program.getEnergon().value(), + program.getCallDeep(), hint); + + program.createContract(value, inOffset, inSize); + + program.step(); + } + break; + case CALL: + case CALLCODE: + case DELEGATECALL: + case STATICCALL: { + program.stackPop(); // use adjustedCallEnergon instead of requested + DataWord codeAddress = program.stackPop(); + DataWord value = op.callHasValue() ? + program.stackPop() : DataWord.ZERO; + + if (program.isStaticCall() && op == CALL && !value.isZero()) + throw new Program.StaticCallModificationException(); + + if (!value.isZero()) { + adjustedCallEnergon = adjustedCallEnergon.add(DataWord.of(energonCosts.getSTIPEND_CALL())); + } + + DataWord inDataOffs = program.stackPop(); + DataWord inDataSize = program.stackPop(); + + DataWord outDataOffs = program.stackPop(); + DataWord outDataSize = program.stackPop(); + + if (logger.isInfoEnabled()) { + hint = "addr: " + toHexString(codeAddress.getLast20Bytes()) + + " Energon: " + adjustedCallEnergon.shortHex() + + " inOff: " + inDataOffs.shortHex() + + " inSize: " + inDataSize.shortHex(); + logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), + String.format("%-12s", op.name()), + program.getEnergon().value(), + program.getCallDeep(), hint); + } + + program.memoryExpand(outDataOffs, outDataSize); + + MessageCall msg = new MessageCall( + op, adjustedCallEnergon, codeAddress, value, inDataOffs, inDataSize, + outDataOffs, outDataSize); + + PrecompiledContracts.PrecompiledContract contract = + PrecompiledContracts.getContractForAddress(codeAddress, blockchainConfig); + + if (!op.callIsStateless()) { + program.getResult().addTouchAccount(codeAddress.getLast20Bytes()); + } + + if (contract != null) { + program.callToPrecompiledAddress(msg, contract); + } else { + program.callToAddress(msg); + } + + program.step(); + } + break; + case RETURN: + case REVERT: { + DataWord offset = program.stackPop(); + DataWord size = program.stackPop(); + + byte[] hReturn = program.memoryChunk(offset.intValueSafe(), size.intValueSafe()); + program.setHReturn(hReturn); + + if (logger.isInfoEnabled()) + hint = "data: " + toHexString(hReturn) + + " offset: " + offset.value() + + " size: " + size.value(); + + program.step(); + program.stop(); + + if (op == REVERT) { + program.getResult().setRevert(); + } + } + break; + case SUICIDE: { + if (program.isStaticCall()) throw new Program.StaticCallModificationException(); + + DataWord address = program.stackPop(); + program.suicide(address); + program.getResult().addTouchAccount(address.getLast20Bytes()); + + if (logger.isInfoEnabled()) + hint = "address: " + toHexString(program.getOwnerAddress().getLast20Bytes()); + + program.stop(); + } + break; + default: + break; + } + + program.setPreviouslyExecutedOp(op.val()); + + if (logger.isInfoEnabled() && !op.isCall()) + logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), + String.format("%-12s", + op.name()), program.getEnergon().value(), + program.getCallDeep(), hint); + + vmCounter++; + } catch (RuntimeException e) { + logger.warn("VM halted: [{}]", e); + program.spendAllEnergon(); + program.resetFutureRefund(); + program.stop(); + throw e; + } finally { + program.fullTrace(); + } + } + + public void play(Program program) { + try { + if (program.byTestingSuite()) return; + + while (!program.isStopped()) { + this.step(program); + } + + } catch (RuntimeException e) { + program.setRuntimeFailure(e); + } catch (StackOverflowError soe) { + logger.error("\n !!! StackOverflowError: update your java run command with -Xss2M (-Xss8M for tests) !!!\n", soe); + System.exit(-1); + } + } + + /** + * Utility to calculate new total memory size needed for an operation. + *
Basically just offset + size, unless size is 0, in which case the result is also 0. + * + * @param offset starting position of the memory + * @param size number of bytes needed + * @return offset + size, unless size is 0. In that case memNeeded is also 0. + */ + private static BigInteger memNeeded(DataWord offset, DataWord size) { + return size.isZero() ? BigInteger.ZERO : offset.value().add(size.value()); + } + + /* + * Dumping the VM state at the current operation in various styles + * - standard Not Yet Implemented + * - standard+ (owner address, program counter, operation, Energon left) + * - pretty (stack, memory, storage, level, contract, + * vmCounter, internalSteps, operation + EnergonBefore, EnergonCost, memWords) + */ + private void dumpLine(OpCode op, long EnergonBefore, long EnergonCost, long memWords, Program program) { + if (config.dumpStyle().equals("standard+")) { + switch (op) { + case STOP: + case RETURN: + case SUICIDE: + + ContractDetails details = program.getStorage() + .getContractDetails(program.getOwnerAddress().getLast20Bytes()); + List storageKeys = new ArrayList<>(details.getStorage().keySet()); + Collections.sort(storageKeys); + + for (DataWord key : storageKeys) { + dumpLogger.trace("{} {}", + toHexString(key.getNoLeadZeroesData()), + toHexString(details.getStorage().get(key).getNoLeadZeroesData())); + } + break; + default: + break; + } + String addressString = toHexString(program.getOwnerAddress().getLast20Bytes()); + String pcString = toHexString(DataWord.of(program.getPC()).getNoLeadZeroesData()); + String opString = toHexString(new byte[]{op.val()}); + String EnergonString = toHexString(program.getEnergon().getNoLeadZeroesData()); + + dumpLogger.trace("{} {} {} {}", addressString, pcString, opString, EnergonString); + } else if (config.dumpStyle().equals("pretty")) { + dumpLogger.trace(" STACK"); + for (DataWord item : program.getStack()) { + dumpLogger.trace("{}", item); + } + dumpLogger.trace(" MEMORY"); + String memoryString = program.memoryToString(); + if (!"".equals(memoryString)) + dumpLogger.trace("{}", memoryString); + + dumpLogger.trace(" STORAGE"); + ContractDetails details = program.getStorage() + .getContractDetails(program.getOwnerAddress().getLast20Bytes()); + List storageKeys = new ArrayList<>(details.getStorage().keySet()); + Collections.sort(storageKeys); + + for (DataWord key : storageKeys) { + dumpLogger.trace("{}: {}", + key.shortHex(), + details.getStorage().get(key).shortHex()); + } + + int level = program.getCallDeep(); + String contract = toHexString(program.getOwnerAddress().getLast20Bytes()); + String internalSteps = String.format("%4s", Integer.toHexString(program.getPC())).replace(' ', '0').toUpperCase(); + dumpLogger.trace("{} | {} | #{} | {} : {} | {} | -{} | {}x32", + level, contract, vmCounter, internalSteps, op, + EnergonBefore, EnergonCost, memWords); + } + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/InternalTransaction.java b/core/src/main/java/org/platon/core/vm/program/InternalTransaction.java new file mode 100644 index 0000000..abf8aef --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/InternalTransaction.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.proto.TransactionType; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static org.apache.commons.lang3.ArrayUtils.isEmpty; +import static org.apache.commons.lang3.ArrayUtils.nullToEmpty; + +public class InternalTransaction extends Transaction { + + private byte[] parentHash; + private int deep; + private int index; + private boolean rejected = false; + private String note; + + public InternalTransaction(byte[] rawData) { + super(rawData); + } + + public InternalTransaction(byte[] parentHash, int deep, int index,byte[] sendAddress,String note,TransactionType type, byte[] value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, byte[] energonPrice, + byte[] energonLimit, byte[] data) { + + super(type,new BigInteger(value),receiveAddress,referenceBlockNum,referenceBlockHash,new BigInteger(energonPrice),new BigInteger(energonLimit),data); + + this.parentHash = parentHash; + this.deep = deep; + this.index = index; + this.sendAddress = nullToEmpty(sendAddress); + this.note = note; + this.parsed = true; + } + + private static byte[] getData(DataWord gasPrice) { + return (gasPrice == null) ? ByteUtil.EMPTY_BYTE_ARRAY : gasPrice.getData(); + } + + public void reject() { + this.rejected = true; + } + + + public int getDeep() { + rlpParse(); + return deep; + } + + public int getIndex() { + rlpParse(); + return index; + } + + public boolean isRejected() { + rlpParse(); + return rejected; + } + + public String getNote() { + rlpParse(); + return note; + } + + @Override + public byte[] getSender() { + rlpParse(); + return sendAddress; + } + + public byte[] getParentHash() { + rlpParse(); + return parentHash; + } + + @Override + //TODO 需要调整成pb + public byte[] getEncoded() { + return null; +// if (rlpEncoded == null) { +// +// byte[] nonce = getNonce(); +// boolean isEmptyNonce = isEmpty(nonce) || (getLength(nonce) == 1 && nonce[0] == 0); +// +// this.rlpEncoded = RLP.encodeList( +// RLP.encodeElement(isEmptyNonce ? null : nonce), +// RLP.encodeElement(this.parentHash), +// RLP.encodeElement(getSender()), +// RLP.encodeElement(getReceiveAddress()), +// RLP.encodeElement(getValue()), +// RLP.encodeElement(getGasPrice()), +// RLP.encodeElement(getGasLimit()), +// RLP.encodeElement(getData()), +// RLP.encodeString(this.note), +// encodeInt(this.deep), +// encodeInt(this.index), +// encodeInt(this.rejected ? 1 : 0) +// ); +// } +// +// return rlpEncoded; + } + + @Override + public byte[] getEncodedRaw() { + return getEncoded(); + } + + //TODO 需要调整成pb + public synchronized void rlpParse() { +// if (parsed) return; +// RLPList decodedTxList = RLP.decode2(rlpEncoded); +// RLPList transaction = (RLPList) decodedTxList.get(0); +// +// setNonce(transaction.get(0).getRLPData()); +// this.parentHash = transaction.get(1).getRLPData(); +// this.sendAddress = transaction.get(2).getRLPData(); +// setReceiveAddress(transaction.get(3).getRLPData()); +// setValue(transaction.get(4).getRLPData()); +// setGasPrice(transaction.get(5).getRLPData()); +// setGasLimit(transaction.get(6).getRLPData()); +// setData(transaction.get(7).getRLPData()); +// this.note = new String(transaction.get(8).getRLPData()); +// this.deep = decodeInt(transaction.get(9).getRLPData()); +// this.index = decodeInt(transaction.get(10).getRLPData()); +// this.rejected = decodeInt(transaction.get(11).getRLPData()) == 1; +// +// this.parsed = true; + } + + + private static byte[] intToBytes(int value) { + return ByteBuffer.allocate(Integer.SIZE / Byte.SIZE) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(value) + .array(); + } + + private static int bytesToInt(byte[] bytes) { + return isEmpty(bytes) ? 0 : ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + private static int decodeInt(byte[] encoded) { + return bytesToInt(encoded); + } + + @Override + public String toString() { + return "TransactionData [" + + " parentHash=" + Hex.toHexString(getParentHash()) + + ", hash=" + Hex.toHexString(getHash()) + + ", gasPrice=" + getEnergonPrice().toString() + + ", gas=" + getEnergonLimit().toString() + + ", sendAddress=" + Hex.toHexString(getSender()) + + ", receiveAddress=" + Hex.toHexString(getReceiveAddress()) + + ", value=" + getValue().toString() + + ", data=" + Hex.toHexString(getData()) + + ", note=" + getNote() + + ", deep=" + getDeep() + + ", index=" + getIndex() + + ", rejected=" + isRejected() + + "]"; + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/Memory.java b/core/src/main/java/org/platon/core/vm/program/Memory.java new file mode 100644 index 0000000..3a0eb3e --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/Memory.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.vm.program.listener.ProgramListener; +import org.platon.core.vm.program.listener.ProgramListenerAware; + +import java.util.LinkedList; +import java.util.List; + +import static java.lang.Math.ceil; +import static java.lang.Math.min; +import static java.lang.String.format; +import static org.platon.common.utils.ByteUtil.oneByteToHexString; + +public class Memory implements ProgramListenerAware { + + private static final int CHUNK_SIZE = 1024; + private static final int WORD_SIZE = 32; + + private List chunks = new LinkedList<>(); + private int softSize; + private ProgramListener programListener; + + @Override + public void setProgramListener(ProgramListener traceListener) { + this.programListener = traceListener; + } + + public byte[] read(int address, int size) { + if (size <= 0) return ByteUtil.EMPTY_BYTE_ARRAY; + + extend(address, size); + byte[] data = new byte[size]; + + int chunkIndex = address / CHUNK_SIZE; + int chunkOffset = address % CHUNK_SIZE; + + int toGrab = data.length; + int start = 0; + + while (toGrab > 0) { + int copied = grabMax(chunkIndex, chunkOffset, toGrab, data, start); + + // read next chunk from the start + ++chunkIndex; + chunkOffset = 0; + + // mark remind + toGrab -= copied; + start += copied; + } + + return data; + } + + public void write(int address, byte[] data, int dataSize, boolean limited) { + if (dataSize <= 0) return; + + if (data.length < dataSize) + dataSize = data.length; + + if (!limited) + extend(address, dataSize); + + int chunkIndex = address / CHUNK_SIZE; + int chunkOffset = address % CHUNK_SIZE; + + int toCapture = 0; + if (limited) + toCapture = (address + dataSize > softSize) ? softSize - address : dataSize; + else + toCapture = dataSize; + + int start = 0; + while (toCapture > 0) { + int captured = captureMax(chunkIndex, chunkOffset, toCapture, data, start); + + // capture next chunk + ++chunkIndex; + chunkOffset = 0; + + // mark remind + toCapture -= captured; + start += captured; + } + + if (programListener != null) programListener.onMemoryWrite(address, data, dataSize); + } + + + public void extendAndWrite(int address, int allocSize, byte[] data) { + extend(address, allocSize); + write(address, data, allocSize, false); + } + + public void extend(int address, int size) { + if (size <= 0) return; + + final int newSize = address + size; + + int toAllocate = newSize - internalSize(); + if (toAllocate > 0) { + addChunks((int) ceil((double) toAllocate / CHUNK_SIZE)); + } + + toAllocate = newSize - softSize; + if (toAllocate > 0) { + toAllocate = (int) ceil((double) toAllocate / WORD_SIZE) * WORD_SIZE; + softSize += toAllocate; + + if (programListener != null) programListener.onMemoryExtend(toAllocate); + } + } + + public DataWord readWord(int address) { + return DataWord.of(read(address, 32)); + } + + // just access expecting all data valid + public byte readByte(int address) { + + int chunkIndex = address / CHUNK_SIZE; + int chunkOffset = address % CHUNK_SIZE; + + byte[] chunk = chunks.get(chunkIndex); + + return chunk[chunkOffset]; + } + + @Override + public String toString() { + + StringBuilder memoryData = new StringBuilder(); + StringBuilder firstLine = new StringBuilder(); + StringBuilder secondLine = new StringBuilder(); + + for (int i = 0; i < softSize; ++i) { + + byte value = readByte(i); + + // Check if value is ASCII + String character = ((byte) 0x20 <= value && value <= (byte) 0x7e) ? new String(new byte[]{value}) : "?"; + firstLine.append(character).append(""); + secondLine.append(oneByteToHexString(value)).append(" "); + + if ((i + 1) % 8 == 0) { + String tmp = format("%4s", Integer.toString(i - 7, 16)).replace(" ", "0"); + memoryData.append("").append(tmp).append(" "); + memoryData.append(firstLine).append(" "); + memoryData.append(secondLine); + if (i + 1 < softSize) memoryData.append("\n"); + firstLine.setLength(0); + secondLine.setLength(0); + } + } + + return memoryData.toString(); + } + + public int size() { + return softSize; + } + + public int internalSize() { + return chunks.size() * CHUNK_SIZE; + } + + public List getChunks() { + return new LinkedList<>(chunks); + } + + private int captureMax(int chunkIndex, int chunkOffset, int size, byte[] src, int srcPos) { + + byte[] chunk = chunks.get(chunkIndex); + int toCapture = min(size, chunk.length - chunkOffset); + + System.arraycopy(src, srcPos, chunk, chunkOffset, toCapture); + return toCapture; + } + + private int grabMax(int chunkIndex, int chunkOffset, int size, byte[] dest, int destPos) { + + byte[] chunk = chunks.get(chunkIndex); + int toGrab = min(size, chunk.length - chunkOffset); + + System.arraycopy(chunk, chunkOffset, dest, destPos, toGrab); + + return toGrab; + } + + private void addChunks(int num) { + for (int i = 0; i < num; ++i) { + chunks.add(new byte[CHUNK_SIZE]); + } + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/Program.java b/core/src/main/java/org/platon/core/vm/program/Program.java new file mode 100644 index 0000000..181af13 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/Program.java @@ -0,0 +1,1322 @@ +package org.platon.core.vm.program; + +import org.apache.commons.lang3.tuple.Pair; +import org.platon.common.utils.ByteComparator; +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.Repository; +import org.platon.core.config.BlockchainConfig; +import org.platon.core.config.CommonConfig; +import org.platon.core.config.CoreConfig; +import org.platon.core.config.SystemConfig; +import org.platon.core.db.ContractDetails; +import org.platon.core.transaction.Transaction; +import org.platon.core.utils.Utils; +import org.platon.core.vm.MessageCall; +import org.platon.core.vm.OpCode; +import org.platon.core.vm.PrecompiledContracts; +import org.platon.core.vm.VM; +import org.platon.core.vm.program.invoke.ProgramInvoke; +import org.platon.core.vm.program.invoke.ProgramInvokeFactory; +import org.platon.core.vm.program.invoke.ProgramInvokeFactoryImpl; +import org.platon.core.vm.program.listener.CompositeProgramListener; +import org.platon.core.vm.program.listener.ProgramListenerAware; +import org.platon.core.vm.program.listener.ProgramStorageChangeListener; +import org.platon.core.vm.trace.ProgramTrace; +import org.platon.core.vm.trace.ProgramTraceListener; +import org.platon.crypto.HashUtil; +import org.platon.common.utils.ByteArraySet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.util.*; + +import static java.lang.StrictMath.min; +import static java.lang.String.format; +import static java.math.BigInteger.ZERO; +import static org.apache.commons.lang3.ArrayUtils.*; +import static org.bouncycastle.util.encoders.Hex.toHexString; +import static org.platon.common.utils.BIUtil.*; +import static org.platon.common.utils.ByteUtil.EMPTY_BYTE_ARRAY; + +/** + * @author Roman Mandeleil + * @since 01.06.2014 + */ +public class Program { + + private static final Logger logger = LoggerFactory.getLogger("VM"); + + /** + * This attribute defines the number of recursive calls allowed in the EVM + * Note: For the JVM to reach this level without a StackOverflow exception, + * ethereumj may need to be started with a JVM argument to increase + * the stack size. For example: -Xss10m + */ + private static final int MAX_DEPTH = 1024; + + //Max size for stack checks + private static final int MAX_STACKSIZE = 1024; + + private Transaction transaction; + + private ProgramInvoke invoke; + private ProgramInvokeFactory programInvokeFactory = new ProgramInvokeFactoryImpl(); + + private ProgramOutListener listener; + private ProgramTraceListener traceListener; + private ProgramStorageChangeListener storageDiffListener = new ProgramStorageChangeListener(); + private CompositeProgramListener programListener = new CompositeProgramListener(); + + private Stack stack; + private Memory memory; + private Storage storage; + private byte[] returnDataBuffer; + + private ProgramResult result = new ProgramResult(); + private ProgramTrace trace = new ProgramTrace(); + + private byte[] codeHash; + private byte[] ops; + private int pc; + private byte lastOp; + private byte previouslyExecutedOp; + private boolean stopped; + private ByteArraySet touchedAccounts = new ByteArraySet(); + + private ProgramPrecompile programPrecompile; + + CommonConfig commonConfig = CommonConfig.getInstance(); + + private CoreConfig coreConfig = CoreConfig.getInstance(); + + private final SystemConfig config; + + private final BlockchainConfig blockchainConfig; + + public Program(byte[] ops, ProgramInvoke programInvoke) { + this(ops, programInvoke, (Transaction) null); + } + + public Program(byte[] ops, ProgramInvoke programInvoke, SystemConfig config) { + this(ops, programInvoke, null, config); + } + + public Program(byte[] ops, ProgramInvoke programInvoke, Transaction transaction) { + this(ops, programInvoke, transaction, SystemConfig.getDefault()); + } + + public Program(byte[] ops, ProgramInvoke programInvoke, Transaction transaction, SystemConfig config) { + this(null, ops, programInvoke, transaction, config); + } + + public Program(byte[] codeHash, byte[] ops, ProgramInvoke programInvoke, Transaction transaction, SystemConfig config) { + this.config = config; + this.invoke = programInvoke; + this.transaction = transaction; + + this.codeHash = codeHash == null || ByteComparator.equals(HashUtil.EMPTY_HASH, codeHash) ? null : codeHash; + this.ops = nullToEmpty(ops); + + traceListener = new ProgramTraceListener(coreConfig.vmTrace()); + this.memory = setupProgramListener(new Memory()); + this.stack = setupProgramListener(new Stack()); + this.storage = setupProgramListener(new Storage(programInvoke)); + this.trace = new ProgramTrace(coreConfig, programInvoke); + this.blockchainConfig = config.getBlockchainConfig().getConfigForBlock(programInvoke.getNumber().longValue()); + } + + public ProgramPrecompile getProgramPrecompile() { + if (programPrecompile == null) { + if (codeHash != null && commonConfig.precompileSource() != null) { + programPrecompile = commonConfig.precompileSource().get(codeHash); + } + if (programPrecompile == null) { + programPrecompile = ProgramPrecompile.compile(ops); + + if (codeHash != null && commonConfig.precompileSource() != null) { + commonConfig.precompileSource().put(codeHash, programPrecompile); + } + } + } + return programPrecompile; + } + + public Program withCommonConfig(CommonConfig commonConfig) { + this.commonConfig = commonConfig; + return this; + } + + public int getCallDeep() { + return invoke.getCallDeep(); + } + + private InternalTransaction addInternalTx(DataWord energonLimit, byte[] senderAddress, byte[] receiveAddress, + BigInteger value, byte[] data, String note) { + + InternalTransaction result = null; + if (transaction != null) { + data = coreConfig.recordInternalTransactionsData() ? data : null; + result = getResult().addInternalTransaction(transaction.getHash(),getCallDeep(),senderAddress,note,transaction.getTransactionType(),value.toByteArray(),receiveAddress, + transaction.getReferenceBlockNum(),transaction.getReferenceBlockHash(),getEnergonPrice(), + energonLimit,data); + } + + return result; + } + + private T setupProgramListener(T programListenerAware) { + if (programListener.isEmpty()) { + programListener.addListener(traceListener); + programListener.addListener(storageDiffListener); + } + + programListenerAware.setProgramListener(programListener); + + return programListenerAware; + } + + public Map getStorageDiff() { + return storageDiffListener.getDiff(); + } + + public byte getOp(int pc) { + return (getLength(ops) <= pc) ? 0 : ops[pc]; + } + + public byte getCurrentOp() { + return isEmpty(ops) ? 0 : ops[pc]; + } + + /** + * Last Op can only be set publicly (no getLastOp method), is used for logging. + */ + public void setLastOp(byte op) { + this.lastOp = op; + } + + /** + * Should be set only after the OP is fully executed. + */ + public void setPreviouslyExecutedOp(byte op) { + this.previouslyExecutedOp = op; + } + + /** + * Returns the last fully executed OP. + */ + public byte getPreviouslyExecutedOp() { + return this.previouslyExecutedOp; + } + + public void stackPush(byte[] data) { + stackPush(DataWord.of(data)); + } + + public void stackPushZero() { + stackPush(DataWord.ZERO); + } + + public void stackPushOne() { + DataWord stackWord = DataWord.ONE; + stackPush(stackWord); + } + + public void stackPush(DataWord stackWord) { + verifyStackOverflow(0, 1); //Sanity Check + stack.push(stackWord); + } + + public Stack getStack() { + return this.stack; + } + + public int getPC() { + return pc; + } + + public void setPC(DataWord pc) { + this.setPC(pc.intValue()); + } + + public void setPC(int pc) { + this.pc = pc; + + if (this.pc >= ops.length) { + stop(); + } + } + + public boolean isStopped() { + return stopped; + } + + public void stop() { + stopped = true; + } + + public void setHReturn(byte[] buff) { + getResult().setHReturn(buff); + } + + public void step() { + setPC(pc + 1); + } + + public byte[] sweep(int n) { + + if (pc + n > ops.length) + stop(); + + byte[] data = Arrays.copyOfRange(ops, pc, pc + n); + pc += n; + if (pc >= ops.length) stop(); + + return data; + } + + public DataWord stackPop() { + return stack.pop(); + } + + /** + * Verifies that the stack is at least stackSize + * + * @param stackSize int + * @throws StackTooSmallException If the stack is + * smaller than stackSize + */ + public void verifyStackSize(int stackSize) { + if (stack.size() < stackSize) { + throw Program.Exception.tooSmallStack(stackSize, stack.size()); + } + } + + public void verifyStackOverflow(int argsReqs, int returnReqs) { + if ((stack.size() - argsReqs + returnReqs) > MAX_STACKSIZE) { + throw new StackTooLargeException("Expected: overflow " + MAX_STACKSIZE + " elements stack limit"); + } + } + + public int getMemSize() { + return memory.size(); + } + + public void memorySave(DataWord addrB, DataWord value) { + memory.write(addrB.intValue(), value.getData(), value.getData().length, false); + } + + public void memorySaveLimited(int addr, byte[] data, int dataSize) { + memory.write(addr, data, dataSize, true); + } + + public void memorySave(int addr, byte[] value) { + memory.write(addr, value, value.length, false); + } + + public void memoryExpand(DataWord outDataOffs, DataWord outDataSize) { + if (!outDataSize.isZero()) { + memory.extend(outDataOffs.intValue(), outDataSize.intValue()); + } + } + + /** + * Allocates a piece of memory and stores value at given offset address + * + * @param addr is the offset address + * @param allocSize size of memory needed to write + * @param value the data to write to memory + */ + public void memorySave(int addr, int allocSize, byte[] value) { + memory.extendAndWrite(addr, allocSize, value); + } + + + public DataWord memoryLoad(DataWord addr) { + return memory.readWord(addr.intValue()); + } + + public DataWord memoryLoad(int address) { + return memory.readWord(address); + } + + public byte[] memoryChunk(int offset, int size) { + return memory.read(offset, size); + } + + /** + * Allocates extra memory in the program for + * a specified size, calculated from a given offset + * + * @param offset the memory address offset + * @param size the number of bytes to allocate + */ + public void allocateMemory(int offset, int size) { + memory.extend(offset, size); + } + + + public void suicide(DataWord obtainerAddress) { + + byte[] owner = getOwnerAddress().getLast20Bytes(); + byte[] obtainer = obtainerAddress.getLast20Bytes(); + BigInteger balance = getStorage().getBalance(owner); + + if (logger.isInfoEnabled()) + logger.info("Transfer to: [{}] heritage: [{}]", + toHexString(obtainer), + balance); + + addInternalTx( null, owner, obtainer, balance, null, "suicide"); + + if (ByteComparator.compareTo(owner, 0, 20, obtainer, 0, 20) == 0) { + // if owner == obtainer just zeroing account according to Yellow Paper + getStorage().addBalance(owner, balance.negate()); + } else { + Utils.transfer(getStorage(), owner, obtainer, balance); + } + + getResult().addDeleteAccount(this.getOwnerAddress()); + } + + public Repository getStorage() { + return this.storage; + } + + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public void createContract(DataWord value, DataWord memStart, DataWord memSize) { + returnDataBuffer = null; // reset return buffer right before the call + + if (getCallDeep() == MAX_DEPTH) { + stackPushZero(); + return; + } + + byte[] senderAddress = this.getOwnerAddress().getLast20Bytes(); + BigInteger endowment = value.value(); + if (isNotCovers(getStorage().getBalance(senderAddress), endowment)) { + stackPushZero(); + return; + } + + // [1] FETCH THE CODE FROM THE MEMORY + byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue()); + + if (logger.isInfoEnabled()) + logger.info("creating a new contract inside contract run: [{}]", toHexString(senderAddress)); + + BlockchainConfig blockchainConfig = config.getBlockchainConfig().getConfigForBlock(getNumber().longValue()); + // actual energon subtract + DataWord energonLimit = blockchainConfig.getCreateEnergon(getEnergon()); + spendEnergon(energonLimit.longValue(), "internal call"); + + // [2] CREATE THE CONTRACT ADDRESS + //TODO 需要一个新的地址生成方式 + byte[] newAddress = null;//HashUtil.calcNewAddr(getOwnerAddress().getLast20Bytes(), nonce); + + Account existingAddr = getStorage().getAccount(newAddress); + boolean contractAlreadyExists = existingAddr != null && existingAddr.isContractExist(); + + if (byTestingSuite()) { + // This keeps track of the contracts created for a test + getResult().addCallCreate(programCode, EMPTY_BYTE_ARRAY, + energonLimit.getNoLeadZeroesData(), + value.getNoLeadZeroesData()); + } + + // [3] UPDATE THE NONCE + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) + Repository track = getStorage().startTracking(); + + //In case of hashing collisions, check for any balance before createAccount() + BigInteger oldBalance = track.getBalance(newAddress); + track.createAccount(newAddress); +// if (blockchainConfig.eip161()) { +// track.increaseNonce(newAddress); +// } + track.addBalance(newAddress, oldBalance); + + // [4] TRANSFER THE BALANCE + BigInteger newBalance = ZERO; + if (!byTestingSuite()) { + track.addBalance(senderAddress, endowment.negate()); + newBalance = track.addBalance(newAddress, endowment); + } + + + // [5] COOK THE INVOKE AND EXECUTE + InternalTransaction internalTx = addInternalTx(getEnergonLimit(), senderAddress, null, endowment, programCode, "create"); + ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( + this, DataWord.of(newAddress), getOwnerAddress(), value, energonLimit, + newBalance, null, track, this.invoke.getBlockStore(), false, byTestingSuite()); + + ProgramResult result = ProgramResult.createEmpty(); + + if (contractAlreadyExists) { + result.setException(new BytecodeExecutionException("Trying to create a contract with existing contract address: 0x" + toHexString(newAddress))); + } else if (isNotEmpty(programCode)) { + VM vm = new VM(coreConfig); + Program program = new Program(programCode, programInvoke, internalTx, config).withCommonConfig(commonConfig); + vm.play(program); + result = program.getResult(); + } + + // 4. CREATE THE CONTRACT OUT OF RETURN + byte[] code = result.getHReturn(); + + long storageCost = getLength(code) * getBlockchainConfig().getEnergonCost().getCREATE_DATA(); + long afterSpend = programInvoke.getEnergon().longValue() - storageCost - result.getEnergonUsed(); + if (afterSpend < 0) { + if (!blockchainConfig.getConstants().createEmptyContractOnOOG()) { + result.setException(Program.Exception.notEnoughSpendingEnergon("No Energon to return just created contract", + storageCost, this)); + } else { + track.saveCode(newAddress, EMPTY_BYTE_ARRAY); + } + } else if (getLength(code) > blockchainConfig.getConstants().getMAX_CONTRACT_SZIE()) { + result.setException(Program.Exception.notEnoughSpendingEnergon("Contract size too large: " + getLength(result.getHReturn()), + storageCost, this)); + } else if (!result.isRevert()){ + result.spendEnergon(storageCost); + track.saveCode(newAddress, code); + } + + getResult().merge(result); + + if (result.getException() != null || result.isRevert()) { + logger.debug("contract run halted by Exception: contract: [{}], exception: [{}]", + toHexString(newAddress), + result.getException()); + + internalTx.reject(); + result.rejectInternalTransactions(); + + track.rollback(); + stackPushZero(); + + if (result.getException() != null) { + return; + } else { + returnDataBuffer = result.getHReturn(); + } + } else { + if (!byTestingSuite()) + track.commit(); + + // IN SUCCESS PUSH THE ADDRESS INTO THE STACK + stackPush(DataWord.of(newAddress)); + } + + // 5. REFUND THE REMAIN Energon + long refundEnergon = energonLimit.longValue() - result.getEnergonUsed(); + if (refundEnergon > 0) { + refundEnergon(refundEnergon, "remain energon from the internal call"); + if (logger.isInfoEnabled()) { + logger.info("The remaining energon is refunded, account: [{}], energon: [{}] ", + toHexString(getOwnerAddress().getLast20Bytes()), + refundEnergon); + } + } + } + + /** + * That method is for internal code invocations + *

+ * - Normal calls invoke a specified contract which updates itself + * - Stateless calls invoke code from another contract, within the context of the caller + * + * @param msg is the message call object + */ + public void callToAddress(MessageCall msg) { + returnDataBuffer = null; // reset return buffer right before the call + + if (getCallDeep() == MAX_DEPTH) { + stackPushZero(); + refundEnergon(msg.getEnergon().longValue(), " call deep limit reach"); + return; + } + + byte[] data = memoryChunk(msg.getInDataOffs().intValue(), msg.getInDataSize().intValue()); + + // FETCH THE SAVED STORAGE + byte[] codeAddress = msg.getCodeAddress().getLast20Bytes(); + byte[] senderAddress = getOwnerAddress().getLast20Bytes(); + byte[] contextAddress = msg.getType().callIsStateless() ? senderAddress : codeAddress; + + if (logger.isInfoEnabled()) + logger.info(msg.getType().name() + " for existing contract: address: [{}], outDataOffs: [{}], outDataSize: [{}] ", + toHexString(contextAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue()); + + Repository track = getStorage().startTracking(); + + // 2.1 PERFORM THE VALUE (endowment) PART + BigInteger endowment = msg.getEndowment().value(); + BigInteger senderBalance = track.getBalance(senderAddress); + if (isNotCovers(senderBalance, endowment)) { + stackPushZero(); + refundEnergon(msg.getEnergon().longValue(), "refund energon from message call"); + return; + } + + + // FETCH THE CODE + byte[] programCode = getStorage().isExist(codeAddress) ? getStorage().getCode(codeAddress) : EMPTY_BYTE_ARRAY; + + + BigInteger contextBalance = ZERO; + if (byTestingSuite()) { + // This keeps track of the calls created for a test + getResult().addCallCreate(data, contextAddress, + msg.getEnergon().getNoLeadZeroesData(), + msg.getEndowment().getNoLeadZeroesData()); + } else { + track.addBalance(senderAddress, endowment.negate()); + contextBalance = track.addBalance(contextAddress, endowment); + } + + // CREATE CALL INTERNAL TRANSACTION + InternalTransaction internalTx = addInternalTx(getEnergonLimit(), senderAddress, contextAddress, endowment, data, "call"); + + ProgramResult result = null; + if (isNotEmpty(programCode)) { + ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( + this, DataWord.of(contextAddress), + msg.getType().callIsDelegate() ? getCallerAddress() : getOwnerAddress(), + msg.getType().callIsDelegate() ? getCallValue() : msg.getEndowment(), + msg.getEnergon(), contextBalance, data, track, this.invoke.getBlockStore(), + msg.getType().callIsStatic() || isStaticCall(), byTestingSuite()); + + VM vm = new VM(coreConfig); + Program program = new Program(getStorage().getCodeHash(codeAddress), programCode, programInvoke, internalTx, config).withCommonConfig(commonConfig); + vm.play(program); + result = program.getResult(); + + getTrace().merge(program.getTrace()); + getResult().merge(result); + + if (result.getException() != null || result.isRevert()) { + logger.debug("contract run halted by Exception: contract: [{}], exception: [{}]", + toHexString(contextAddress), + result.getException()); + + internalTx.reject(); + result.rejectInternalTransactions(); + + track.rollback(); + stackPushZero(); + + if (result.getException() != null) { + return; + } + } else { + // 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK + track.commit(); + stackPushOne(); + } + + if (byTestingSuite()) { + logger.info("Testing run, skipping storage diff listener"); + } else if (Arrays.equals(transaction.getReceiveAddress(), internalTx.getReceiveAddress())) { + storageDiffListener.merge(program.getStorageDiff()); + } + } else { + // 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK + track.commit(); + stackPushOne(); + } + + // 3. APPLY RESULTS: result.getHReturn() into out_memory allocated + if (result != null) { + byte[] buffer = result.getHReturn(); + int offset = msg.getOutDataOffs().intValue(); + int size = msg.getOutDataSize().intValue(); + + memorySaveLimited(offset, buffer, size); + + returnDataBuffer = buffer; + } + + // 5. REFUND THE REMAIN Energon + if (result != null) { + BigInteger refundEnergon = msg.getEnergon().value().subtract(toBI(result.getEnergonUsed())); + if (isPositive(refundEnergon)) { + refundEnergon(refundEnergon.longValue(), "remaining Energon from the internal call"); + if (logger.isInfoEnabled()) + logger.info("The remaining Energon refunded, account: [{}], Energon: [{}] ", + toHexString(senderAddress), + refundEnergon.toString()); + } + } else { + refundEnergon(msg.getEnergon().longValue(), "remaining Energon from the internal call"); + } + } + + public void spendEnergon(long energonValue, String cause) { + if (logger.isDebugEnabled()) { + logger.debug("[{}] Spent for cause: [{}], Energon: [{}]", invoke.hashCode(), cause, energonValue); + } + + if (getEnergonLong() < energonValue) { + throw Program.Exception.notEnoughSpendingEnergon(cause, energonValue, this); + } + getResult().spendEnergon(energonValue); + } + + public void spendAllEnergon() { + spendEnergon(getEnergon().longValue(), "Spending all remaining"); + } + + public void refundEnergon(long energonValue, String cause) { + logger.info("[{}] Refund for cause: [{}], Energon: [{}]", invoke.hashCode(), cause, energonValue); + getResult().refundEnergon(energonValue); + } + + public void futureRefundEnergon(long energonValue) { + logger.info("Future refund added: [{}]", energonValue); + getResult().addFutureRefund(energonValue); + } + + public void resetFutureRefund() { + getResult().resetFutureRefund(); + } + + public void storageSave(DataWord word1, DataWord word2) { + storageSave(word1.getData(), word2.getData()); + } + + public void storageSave(byte[] key, byte[] val) { + DataWord keyWord = DataWord.of(key); + DataWord valWord = DataWord.of(val); + getStorage().addStorageRow(getOwnerAddress().getLast20Bytes(), keyWord, valWord); + } + + public byte[] getCode() { + return ops; + } + + public byte[] getCodeAt(DataWord address) { + byte[] code = invoke.getRepository().getCode(address.getLast20Bytes()); + return nullToEmpty(code); + } + + public byte[] getCodeHashAt(DataWord address) { + byte[] code = invoke.getRepository().getCodeHash(address.getLast20Bytes()); + return nullToEmpty(code); + } + + public DataWord getOwnerAddress() { + return invoke.getOwnerAddress(); + } + + public DataWord getBlockHash(int index) { + return index < this.getNumber().longValue() && index >= Math.max(256, this.getNumber().intValue()) - 256 ? + DataWord.of(this.invoke.getBlockStore().getBlockHashByNumber(index, getPrevHash().getData())) : + DataWord.ZERO; + } + + public DataWord getBalance(DataWord address) { + BigInteger balance = getStorage().getBalance(address.getLast20Bytes()); + return DataWord.of(balance.toByteArray()); + } + + public DataWord getOriginAddress() { + return invoke.getOriginAddress(); + } + + public DataWord getCallerAddress() { + return invoke.getCallerAddress(); + } + + public DataWord getEnergonPrice() { + return invoke.getMinEnergonPrice(); + } + + public long getEnergonLong() { + return invoke.getEnergonLong() - getResult().getEnergonUsed(); + } + + public DataWord getEnergon() { + return DataWord.of(invoke.getEnergonLong() - getResult().getEnergonUsed()); + } + + public DataWord getCallValue() { + return invoke.getCallValue(); + } + + public DataWord getDataSize() { + return invoke.getDataSize(); + } + + public DataWord getDataValue(DataWord index) { + return invoke.getDataValue(index); + } + + public byte[] getDataCopy(DataWord offset, DataWord length) { + return invoke.getDataCopy(offset, length); + } + + public DataWord getReturnDataBufferSize() { + return DataWord.of(getReturnDataBufferSizeI()); + } + + private int getReturnDataBufferSizeI() { + return returnDataBuffer == null ? 0 : returnDataBuffer.length; + } + + public byte[] getReturnDataBufferData(DataWord off, DataWord size) { + if ((long) off.intValueSafe() + size.intValueSafe() > getReturnDataBufferSizeI()) return null; + return returnDataBuffer == null ? new byte[0] : + Arrays.copyOfRange(returnDataBuffer, off.intValueSafe(), off.intValueSafe() + size.intValueSafe()); + } + + public DataWord storageLoad(DataWord key) { + return getStorage().getStorageValue(getOwnerAddress().getLast20Bytes(), key); + } + + public DataWord getPrevHash() { + return invoke.getPrevHash(); + } + + public DataWord getCoinbase() { + return invoke.getCoinbase(); + } + + public DataWord getTimestamp() { + return invoke.getTimestamp(); + } + + public DataWord getNumber() { + return invoke.getNumber(); + } + + public BlockchainConfig getBlockchainConfig() { + return blockchainConfig; + } + + public DataWord getDifficulty() { + return invoke.getDifficulty(); + } + + public DataWord getEnergonLimit() { + return invoke.getEnergonLimit(); + } + + public boolean isStaticCall() { + return invoke.isStaticCall(); + } + + public ProgramResult getResult() { + return result; + } + + public void setRuntimeFailure(RuntimeException e) { + getResult().setException(e); + } + + public String memoryToString() { + return memory.toString(); + } + + public void fullTrace() { + + if (logger.isTraceEnabled() || listener != null) { + + StringBuilder stackData = new StringBuilder(); + for (int i = 0; i < stack.size(); ++i) { + stackData.append(" ").append(stack.get(i)); + if (i < stack.size() - 1) stackData.append("\n"); + } + + if (stackData.length() > 0) stackData.insert(0, "\n"); + + ContractDetails contractDetails = getStorage(). + getContractDetails(getOwnerAddress().getLast20Bytes()); + StringBuilder storageData = new StringBuilder(); + if (contractDetails != null) { + try { + List storageKeys = new ArrayList<>(contractDetails.getStorage().keySet()); + Collections.sort(storageKeys); + for (DataWord key : storageKeys) { + storageData.append(" ").append(key).append(" -> "). + append(contractDetails.getStorage().get(key)).append("\n"); + } + if (storageData.length() > 0) storageData.insert(0, "\n"); + } catch (java.lang.Exception e) { + storageData.append("Failed to print storage: ").append(e.getMessage()); + } + } + + StringBuilder memoryData = new StringBuilder(); + StringBuilder oneLine = new StringBuilder(); + if (memory.size() > 320) + memoryData.append("... Memory Folded.... ") + .append("(") + .append(memory.size()) + .append(") bytes"); + else + for (int i = 0; i < memory.size(); ++i) { + + byte value = memory.readByte(i); + oneLine.append(ByteUtil.oneByteToHexString(value)).append(" "); + + if ((i + 1) % 16 == 0) { + String tmp = format("[%4s]-[%4s]", Integer.toString(i - 15, 16), + Integer.toString(i, 16)).replace(" ", "0"); + memoryData.append("").append(tmp).append(" "); + memoryData.append(oneLine); + if (i < memory.size()) memoryData.append("\n"); + oneLine.setLength(0); + } + } + if (memoryData.length() > 0) memoryData.insert(0, "\n"); + + StringBuilder opsString = new StringBuilder(); + for (int i = 0; i < ops.length; ++i) { + + String tmpString = Integer.toString(ops[i] & 0xFF, 16); + tmpString = tmpString.length() == 1 ? "0" + tmpString : tmpString; + + if (i != pc) + opsString.append(tmpString); + else + opsString.append(" >>").append(tmpString).append(""); + + } + if (pc >= ops.length) opsString.append(" >>"); + if (opsString.length() > 0) opsString.insert(0, "\n "); + + logger.trace(" -- OPS -- {}", opsString); + logger.trace(" -- STACK -- {}", stackData); + logger.trace(" -- MEMORY -- {}", memoryData); + logger.trace(" -- STORAGE -- {}\n", storageData); + logger.trace("\n Spent Energon: [{}]/[{}]\n Left Energon: [{}]\n", + getResult().getEnergonUsed(), + invoke.getEnergon().longValue(), + getEnergon().longValue()); + + StringBuilder globalOutput = new StringBuilder("\n"); + if (stackData.length() > 0) stackData.append("\n"); + + if (pc != 0) + globalOutput.append("[Op: ").append(OpCode.code(lastOp).name()).append("]\n"); + + globalOutput.append(" -- OPS -- ").append(opsString).append("\n"); + globalOutput.append(" -- STACK -- ").append(stackData).append("\n"); + globalOutput.append(" -- MEMORY -- ").append(memoryData).append("\n"); + globalOutput.append(" -- STORAGE -- ").append(storageData).append("\n"); + + if (getResult().getHReturn() != null) + globalOutput.append("\n HReturn: ").append( + toHexString(getResult().getHReturn())); + + // sophisticated assumption that msg.data != codedata + // means we are calling the contract not creating it + byte[] txData = invoke.getDataCopy(DataWord.ZERO, getDataSize()); + if (!Arrays.equals(txData, ops)) + globalOutput.append("\n msg.data: ").append(toHexString(txData)); + globalOutput.append("\n\n Spent Energon: ").append(getResult().getEnergonUsed()); + + if (listener != null) + listener.output(globalOutput.toString()); + } + } + + public void saveOpTrace() { + if (this.pc < ops.length) { + trace.addOp(ops[pc], pc, getCallDeep(), getEnergon(), traceListener.resetActions()); + } + } + + public ProgramTrace getTrace() { + return trace; + } + + static String formatBinData(byte[] binData, int startPC) { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < binData.length; i += 16) { + ret.append(Utils.align("" + Integer.toHexString(startPC + (i)) + ":", ' ', 8, false)); + ret.append(toHexString(binData, i, min(16, binData.length - i))).append('\n'); + } + return ret.toString(); + } + + public static String stringifyMultiline(byte[] code) { + int index = 0; + StringBuilder sb = new StringBuilder(); + BitSet mask = buildReachableBytecodesMask(code); + ByteArrayOutputStream binData = new ByteArrayOutputStream(); + int binDataStartPC = -1; + + while (index < code.length) { + final byte opCode = code[index]; + OpCode op = OpCode.code(opCode); + + if (!mask.get(index)) { + if (binDataStartPC == -1) { + binDataStartPC = index; + } + binData.write(code[index]); + index++; + if (index < code.length) continue; + } + + if (binDataStartPC != -1) { + sb.append(formatBinData(binData.toByteArray(), binDataStartPC)); + binDataStartPC = -1; + binData = new ByteArrayOutputStream(); + if (index == code.length) continue; + } + + sb.append(Utils.align("" + Integer.toHexString(index) + ":", ' ', 8, false)); + + if (op == null) { + sb.append(": ").append(0xFF & opCode).append("\n"); + index++; + continue; + } + + if (op.name().startsWith("PUSH")) { + sb.append(' ').append(op.name()).append(' '); + + int nPush = op.val() - OpCode.PUSH1.val() + 1; + byte[] data = Arrays.copyOfRange(code, index + 1, index + nPush + 1); + BigInteger bi = new BigInteger(1, data); + sb.append("0x").append(bi.toString(16)); + if (bi.bitLength() <= 32) { + sb.append(" (").append(new BigInteger(1, data).toString()).append(") "); + } + + index += nPush + 1; + } else { + sb.append(' ').append(op.name()); + index++; + } + sb.append('\n'); + } + + return sb.toString(); + } + + static class ByteCodeIterator { + byte[] code; + int pc; + + public ByteCodeIterator(byte[] code) { + this.code = code; + } + + public void setPC(int pc) { + this.pc = pc; + } + + public int getPC() { + return pc; + } + + public OpCode getCurOpcode() { + return pc < code.length ? OpCode.code(code[pc]) : null; + } + + public boolean isPush() { + return getCurOpcode() != null ? getCurOpcode().name().startsWith("PUSH") : false; + } + + public byte[] getCurOpcodeArg() { + if (isPush()) { + int nPush = getCurOpcode().val() - OpCode.PUSH1.val() + 1; + byte[] data = Arrays.copyOfRange(code, pc + 1, pc + nPush + 1); + return data; + } else { + return new byte[0]; + } + } + + public boolean next() { + pc += 1 + getCurOpcodeArg().length; + return pc < code.length; + } + } + + static BitSet buildReachableBytecodesMask(byte[] code) { + NavigableSet gotos = new TreeSet<>(); + ByteCodeIterator it = new ByteCodeIterator(code); + BitSet ret = new BitSet(code.length); + int lastPush = 0; + int lastPushPC = 0; + do { + ret.set(it.getPC()); // reachable bytecode + if (it.isPush()) { + lastPush = new BigInteger(1, it.getCurOpcodeArg()).intValue(); + lastPushPC = it.getPC(); + } + if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.JUMPI) { + if (it.getPC() != lastPushPC + 1) { + // some PC arithmetic we totally can't deal with + // assuming all bytecodes are reachable as a fallback + ret.set(0, code.length); + return ret; + } + int jumpPC = lastPush; + if (!ret.get(jumpPC)) { + // code was not explored yet + gotos.add(jumpPC); + } + } + if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.RETURN || + it.getCurOpcode() == OpCode.STOP) { + if (gotos.isEmpty()) break; + it.setPC(gotos.pollFirst()); + } + } while (it.next()); + return ret; + } + + public static String stringify(byte[] code) { + int index = 0; + StringBuilder sb = new StringBuilder(); + BitSet mask = buildReachableBytecodesMask(code); + String binData = ""; + + while (index < code.length) { + final byte opCode = code[index]; + OpCode op = OpCode.code(opCode); + + if (op == null) { + sb.append(" : ").append(0xFF & opCode).append(" "); + index++; + continue; + } + + if (op.name().startsWith("PUSH")) { + sb.append(' ').append(op.name()).append(' '); + + int nPush = op.val() - OpCode.PUSH1.val() + 1; + byte[] data = Arrays.copyOfRange(code, index + 1, index + nPush + 1); + BigInteger bi = new BigInteger(1, data); + sb.append("0x").append(bi.toString(16)).append(" "); + + index += nPush + 1; + } else { + sb.append(' ').append(op.name()); + index++; + } + } + + return sb.toString(); + } + + + public void addListener(ProgramOutListener listener) { + this.listener = listener; + } + + public int verifyJumpDest(DataWord nextPC) { + if (nextPC.bytesOccupied() > 4) { + throw Program.Exception.badJumpDestination(-1); + } + int ret = nextPC.intValue(); + if (!getProgramPrecompile().hasJumpDest(ret)) { + throw Program.Exception.badJumpDestination(ret); + } + return ret; + } + + public void callToPrecompiledAddress(MessageCall msg, PrecompiledContracts.PrecompiledContract contract) { + returnDataBuffer = null; // reset return buffer right before the call + + if (getCallDeep() == MAX_DEPTH) { + stackPushZero(); + this.refundEnergon(msg.getEnergon().longValue(), " call deep limit reach"); + return; + } + + Repository track = getStorage().startTracking(); + + byte[] senderAddress = this.getOwnerAddress().getLast20Bytes(); + byte[] codeAddress = msg.getCodeAddress().getLast20Bytes(); + byte[] contextAddress = msg.getType().callIsStateless() ? senderAddress : codeAddress; + + + BigInteger endowment = msg.getEndowment().value(); + BigInteger senderBalance = track.getBalance(senderAddress); + if (senderBalance.compareTo(endowment) < 0) { + stackPushZero(); + this.refundEnergon(msg.getEnergon().longValue(), "refund Energon from message call"); + return; + } + + byte[] data = this.memoryChunk(msg.getInDataOffs().intValue(), + msg.getInDataSize().intValue()); + + // Charge for endowment - is not reversible by rollback + Utils.transfer(track, senderAddress, contextAddress, msg.getEndowment().value()); + + if (byTestingSuite()) { + // This keeps track of the calls created for a test + this.getResult().addCallCreate(data, + msg.getCodeAddress().getLast20Bytes(), + msg.getEnergon().getNoLeadZeroesData(), + msg.getEndowment().getNoLeadZeroesData()); + + stackPushOne(); + return; + } + + + long requiredEnergon = contract.getEnergonForData(data); + if (requiredEnergon > msg.getEnergon().longValue()) { + + this.refundEnergon(0, "call pre-compiled"); //matches cpp logic + this.stackPushZero(); + track.rollback(); + } else { + + if (logger.isDebugEnabled()) + logger.debug("Call {}(data = {})", contract.getClass().getSimpleName(), toHexString(data)); + + Pair out = contract.execute(data); + + if (out.getLeft()) { // success + this.refundEnergon(msg.getEnergon().longValue() - requiredEnergon, "call pre-compiled"); + this.stackPushOne(); + returnDataBuffer = out.getRight(); + track.commit(); + } else { + // spend all Energon on failure, push zero and revert state changes + this.refundEnergon(0, "call pre-compiled"); + this.stackPushZero(); + track.rollback(); + } + + this.memorySave(msg.getOutDataOffs().intValue(), msg.getOutDataSize().intValueSafe(), out.getRight()); + } + } + + public boolean byTestingSuite() { + return invoke.byTestingSuite(); + } + + public interface ProgramOutListener { + void output(String out); + } + + /** + * Denotes problem when executing Ethereum bytecode. + * From blockchain and peer perspective this is quite normal situation + * and doesn't mean exceptional situation in terms of the program execution + */ + @SuppressWarnings("serial") + public static class BytecodeExecutionException extends RuntimeException { + public BytecodeExecutionException(String message) { + super(message); + } + } + + @SuppressWarnings("serial") + public static class OutOfEnergonException extends BytecodeExecutionException { + + public OutOfEnergonException(String message, Object... args) { + super(format(message, args)); + } + } + + @SuppressWarnings("serial") + public static class IllegalOperationException extends BytecodeExecutionException { + + public IllegalOperationException(String message, Object... args) { + super(format(message, args)); + } + } + + @SuppressWarnings("serial") + public static class BadJumpDestinationException extends BytecodeExecutionException { + + public BadJumpDestinationException(String message, Object... args) { + super(format(message, args)); + } + } + + @SuppressWarnings("serial") + public static class StackTooSmallException extends BytecodeExecutionException { + + public StackTooSmallException(String message, Object... args) { + super(format(message, args)); + } + } + + @SuppressWarnings("serial") + public static class ReturnDataCopyIllegalBoundsException extends BytecodeExecutionException { + public ReturnDataCopyIllegalBoundsException(DataWord off, DataWord size, long returnDataSize) { + super(String.format("Illegal RETURNDATACOPY arguments: offset (%s) + size (%s) > RETURNDATASIZE (%d)", off, size, returnDataSize)); + } + } + + @SuppressWarnings("serial") + public static class StaticCallModificationException extends BytecodeExecutionException { + public StaticCallModificationException() { + super("Attempt to call a state modifying opcode inside STATICCALL"); + } + } + + + public static class Exception { + + public static OutOfEnergonException notEnoughOpEnergon(OpCode op, long opEnergon, long programEnergon) { + return new OutOfEnergonException("Not enough Energon for '%s' operation executing: opEnergon[%d], programEnergon[%d];", op, opEnergon, programEnergon); + } + + public static OutOfEnergonException notEnoughOpEnergon(OpCode op, DataWord opEnergon, DataWord programEnergon) { + return notEnoughOpEnergon(op, opEnergon.longValue(), programEnergon.longValue()); + } + + public static OutOfEnergonException notEnoughOpEnergon(OpCode op, BigInteger opEnergon, BigInteger programEnergon) { + return notEnoughOpEnergon(op, opEnergon.longValue(), programEnergon.longValue()); + } + + public static OutOfEnergonException notEnoughSpendingEnergon(String cause, long energonValue, Program program) { + return new OutOfEnergonException("Not enough Energon for '%s' cause spending: invokeEnergon[%d], Energon[%d], usedEnergon[%d];", + cause, program.invoke.getEnergon().longValue(), energonValue, program.getResult().getEnergonUsed()); + } + + public static OutOfEnergonException EnergonOverflow(BigInteger actualEnergon, BigInteger energonLimit) { + return new OutOfEnergonException("Energon value overflow: actualEnergon[%d], EnergonLimit[%d];", actualEnergon.longValue(), energonLimit.longValue()); + } + + public static IllegalOperationException invalidOpCode(byte... opCode) { + return new IllegalOperationException("Invalid operation code: opCode[%s];", toHexString(opCode, 0, 1)); + } + + public static BadJumpDestinationException badJumpDestination(int pc) { + return new BadJumpDestinationException("Operation with pc isn't 'JUMPDEST': PC[%d];", pc); + } + + public static StackTooSmallException tooSmallStack(int expectedSize, int actualSize) { + return new StackTooSmallException("Expected stack size %d but actual %d;", expectedSize, actualSize); + } + } + + @SuppressWarnings("serial") + public class StackTooLargeException extends BytecodeExecutionException { + public StackTooLargeException(String message) { + super(message); + } + } + + /** + * used mostly for testing reasons + */ + public byte[] getMemory() { + return memory.read(0, memory.size()); + } + + /** + * used mostly for testing reasons + */ + public void initMem(byte[] data) { + this.memory.write(0, data, data.length, false); + } + +} diff --git a/core/src/main/java/org/platon/core/vm/program/ProgramPrecompile.java b/core/src/main/java/org/platon/core/vm/program/ProgramPrecompile.java new file mode 100644 index 0000000..20f9802 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/ProgramPrecompile.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.platon.common.BasicPbCodec; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteUtil; +import org.platon.core.vm.OpCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created by Anton Nashatyrev on 06.02.2017. + */ +public class ProgramPrecompile { + + private static final Logger logger = LoggerFactory.getLogger(ProgramPrecompile.class); + private static final int version = 1; + + private Set jumpdest = new HashSet<>(); + + public byte[] serialize() { + byte[][] jdBytes = new byte[jumpdest.size() + 1][]; + int cnt = 0; + jdBytes[cnt++] = BasicPbCodec.encodeInt(version); + for (Integer dst : jumpdest) { + jdBytes[cnt++] = BasicPbCodec.encodeInt(dst); + } + + return BasicPbCodec.encodeBytesList(jdBytes); + } + + public static ProgramPrecompile deserialize(byte[] stream) { + List bytesList = null; + try { + bytesList = BasicPbCodec.decodeBytesList(stream); + int ver = ByteUtil.byteArrayToInt(bytesList.get(0).getData()); + if (ver != version) return null; + ProgramPrecompile ret = new ProgramPrecompile(); + for (int i = 1; i < bytesList.size(); i++) { + ret.jumpdest.add(ByteUtil.byteArrayToInt(bytesList.get(i).getData())); + } + return ret; + } catch (InvalidProtocolBufferException e) { + logger.error("ProgramPrecompile deserialize error",e); + return null; + } + } + + public static ProgramPrecompile compile(byte[] ops) { + ProgramPrecompile ret = new ProgramPrecompile(); + for (int i = 0; i < ops.length; ++i) { + + OpCode op = OpCode.code(ops[i]); + if (op == null) continue; + + if (op.equals(OpCode.JUMPDEST)) ret.jumpdest.add(i); + + if (op.asInt() >= OpCode.PUSH1.asInt() && op.asInt() <= OpCode.PUSH32.asInt()) { + i += op.asInt() - OpCode.PUSH1.asInt() + 1; + } + } + return ret; + } + + public boolean hasJumpDest(int pc) { + return jumpdest.contains(pc); + } + + public static void main(String[] args) throws Exception { + ProgramPrecompile pp = new ProgramPrecompile(); + pp.jumpdest.add(100); + pp.jumpdest.add(200); + byte[] bytes = pp.serialize(); + + ProgramPrecompile pp1 = ProgramPrecompile.deserialize(bytes); + System.out.println(pp1.jumpdest); + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/ProgramResult.java b/core/src/main/java/org/platon/core/vm/program/ProgramResult.java new file mode 100644 index 0000000..54ddde8 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/ProgramResult.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.transaction.LogInfo; +import org.platon.core.transaction.proto.TransactionType; +import org.platon.core.vm.CallCreate; +import org.platon.common.utils.ByteArraySet; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.apache.commons.collections4.CollectionUtils.size; + + +/** + * @author Roman Mandeleil + * @since 07.06.2014 + */ +public class ProgramResult { + + private long energonUsed; + private byte[] hReturn = ByteUtil.EMPTY_BYTE_ARRAY; + private RuntimeException exception; + private boolean revert; + + private Set deleteAccounts; + private ByteArraySet touchedAccounts = new ByteArraySet(); + private List internalTransactions; + private List logInfoList; + private long futureRefund = 0; + + /* + * for testing runs , + * call/create is not executed + * but dummy recorded + */ + private List callCreateList; + + public void spendEnergon(long energon) { + energonUsed += energon; + } + + public void setRevert() { + this.revert = true; + } + + public boolean isRevert() { + return revert; + } + + public void refundEnergon(long energon) { + energonUsed -= energon; + } + + public void setHReturn(byte[] hReturn) { + this.hReturn = hReturn; + + } + + public byte[] getHReturn() { + return hReturn; + } + + public RuntimeException getException() { + return exception; + } + + public long getEnergonUsed() { + return energonUsed; + } + + public void setException(RuntimeException exception) { + this.exception = exception; + } + + public Set getDeleteAccounts() { + if (deleteAccounts == null) { + deleteAccounts = new HashSet<>(); + } + return deleteAccounts; + } + + public void addDeleteAccount(DataWord address) { + getDeleteAccounts().add(address); + } + + public void addDeleteAccounts(Set accounts) { + if (!isEmpty(accounts)) { + getDeleteAccounts().addAll(accounts); + } + } + + public void addTouchAccount(byte[] addr) { + touchedAccounts.add(addr); + } + + public Set getTouchedAccounts() { + return touchedAccounts; + } + + public void addTouchAccounts(Set accounts) { + if (!isEmpty(accounts)) { + getTouchedAccounts().addAll(accounts); + } + } + + public List getLogInfoList() { + if (logInfoList == null) { + logInfoList = new ArrayList<>(); + } + return logInfoList; + } + + public void addLogInfo(LogInfo logInfo) { + getLogInfoList().add(logInfo); + } + + public void addLogInfos(List logInfos) { + if (!isEmpty(logInfos)) { + getLogInfoList().addAll(logInfos); + } + } + + public List getCallCreateList() { + if (callCreateList == null) { + callCreateList = new ArrayList<>(); + } + return callCreateList; + } + + public void addCallCreate(byte[] data, byte[] destination, byte[] energonLimit, byte[] value) { + getCallCreateList().add(new CallCreate(data, destination, energonLimit, value)); + } + + public List getInternalTransactions() { + if (internalTransactions == null) { + internalTransactions = new ArrayList<>(); + } + return internalTransactions; + } + + public InternalTransaction addInternalTransaction(byte[] parentHash, int deep,byte[] sendAddress,String note,TransactionType type, byte[] value, byte[] receiveAddress, + long referenceBlockNum, byte[] referenceBlockHash, DataWord energonPrice, + DataWord energonLimit, byte[] data) { + InternalTransaction transaction = new InternalTransaction(parentHash,deep,size(internalTransactions),sendAddress,note,type,value,receiveAddress,referenceBlockNum,referenceBlockHash,energonPrice.getData(),energonLimit.getData(),data); + getInternalTransactions().add(transaction); + return transaction; + } + + public void addInternalTransactions(List internalTransactions) { + getInternalTransactions().addAll(internalTransactions); + } + + public void rejectInternalTransactions() { + for (InternalTransaction internalTx : getInternalTransactions()) { + internalTx.reject(); + } + } + + public void addFutureRefund(long energonValue) { + futureRefund += energonValue; + } + + public long getFutureRefund() { + return futureRefund; + } + + public void resetFutureRefund() { + futureRefund = 0; + } + + public void merge(ProgramResult another) { + addInternalTransactions(another.getInternalTransactions()); + if (another.getException() == null && !another.isRevert()) { + addDeleteAccounts(another.getDeleteAccounts()); + addLogInfos(another.getLogInfoList()); + addFutureRefund(another.getFutureRefund()); + addTouchAccounts(another.getTouchedAccounts()); + } + } + + public static ProgramResult createEmpty() { + ProgramResult result = new ProgramResult(); + result.setHReturn(ByteUtil.EMPTY_BYTE_ARRAY); + return result; + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/Stack.java b/core/src/main/java/org/platon/core/vm/program/Stack.java new file mode 100644 index 0000000..8a9a963 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/Stack.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + + +import org.platon.common.wrapper.DataWord; +import org.platon.core.vm.program.listener.ProgramListener; +import org.platon.core.vm.program.listener.ProgramListenerAware; + +public class Stack extends java.util.Stack implements ProgramListenerAware { + + private ProgramListener programListener; + + @Override + public void setProgramListener(ProgramListener listener) { + this.programListener = listener; + } + + @Override + public synchronized DataWord pop() { + if (programListener != null) programListener.onStackPop(); + return super.pop(); + } + + @Override + public DataWord push(DataWord item) { + if (programListener != null) programListener.onStackPush(item); + return super.push(item); + } + + public void swap(int from, int to) { + if (isAccessible(from) && isAccessible(to) && (from != to)) { + if (programListener != null) programListener.onStackSwap(from, to); + DataWord tmp = get(from); + set(from, set(to, tmp)); + } + } + + private boolean isAccessible(int from) { + return from >= 0 && from < size(); + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/Storage.java b/core/src/main/java/org/platon/core/vm/program/Storage.java new file mode 100644 index 0000000..0e92d71 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/Storage.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program; + +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Account; +import org.platon.core.Repository; +import org.platon.core.block.Block; +import org.platon.core.db.ContractDetails; +import org.platon.core.vm.program.invoke.ProgramInvoke; +import org.platon.core.vm.program.listener.ProgramListener; +import org.platon.core.vm.program.listener.ProgramListenerAware; + +import javax.annotation.Nullable; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Storage implements Repository, ProgramListenerAware { + + private final Repository repository; + private final DataWord address; + private ProgramListener programListener; + + public Storage(ProgramInvoke programInvoke) { + this.address = programInvoke.getOwnerAddress(); + this.repository = programInvoke.getRepository(); + } + + @Override + public void setProgramListener(ProgramListener listener) { + this.programListener = listener; + } + + @Override + public boolean isContract(byte[] addr) { + return false; + } + + @Override + public Account createAccount(byte[] addr) { + return repository.createAccount(addr); + } + + @Override + public boolean isExist(byte[] addr) { + return repository.isExist(addr); + } + + @Override + public Account getAccount(byte[] addr) { + return repository.getAccount(addr); + } + + @Override + public void delete(byte[] addr) { + if (canListenTrace(addr)) programListener.onStorageClear(); + repository.delete(addr); + } + + @Override + public ContractDetails getContractDetails(byte[] addr) { + return repository.getContractDetails(addr); + } + + @Override + public boolean hasContractDetails(byte[] addr) { + return repository.hasContractDetails(addr); + } + + @Override + public void saveCode(byte[] addr, byte[] code) { + repository.saveCode(addr, code); + } + + @Override + public byte[] getCode(byte[] addr) { + return repository.getCode(addr); + } + + @Override + public byte[] getCodeHash(byte[] addr) { + return repository.getCodeHash(addr); + } + + @Override + public void addStorageRow(byte[] addr, DataWord key, DataWord value) { + if (canListenTrace(addr)) programListener.onStoragePut(key, value); + repository.addStorageRow(addr, key, value); + } + + private boolean canListenTrace(byte[] address) { + return (programListener != null) && this.address.equals(DataWord.of(address)); + } + + @Override + public DataWord getStorageValue(byte[] addr, DataWord key) { + return repository.getStorageValue(addr, key); + } + + @Override + public BigInteger getBalance(byte[] addr) { + return repository.getBalance(addr); + } + + @Override + public BigInteger addBalance(byte[] addr, BigInteger value) { + return repository.addBalance(addr, value); + } + + @Override + public BigInteger subBalance(byte[] addr, BigInteger value) { + return null; + } + + @Override + public Set getAccountsKeys() { + return repository.getAccountsKeys(); + } + + @Override + public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) { + repository.dumpState(block, gasUsed, txNumber, txHash); + } + + @Override + public Repository startTracking() { + return repository.startTracking(); + } + + @Override + public void flush() { + repository.flush(); + } + + @Override + public void flushNoReconnect() { + throw new UnsupportedOperationException(); + } + + + @Override + public void commit() { + repository.commit(); + } + + @Override + public void rollback() { + repository.rollback(); + } + + @Override + public void syncToRoot(byte[] root) { + repository.syncToRoot(root); + } + + @Override + public boolean isClosed() { + return repository.isClosed(); + } + + @Override + public void close() { + repository.close(); + } + + @Override + public void reset() { + repository.reset(); + } + + @Override + public void updateBatch(HashMap accountStates, HashMap contractDetails) { + for (ByteArrayWrapper address : contractDetails.keySet()) { + if (!canListenTrace(address.getData())) return; + + ContractDetails details = contractDetails.get(address); + if (details.isDeleted()) { + programListener.onStorageClear(); + } else if (details.isDirty()) { + for (Map.Entry entry : details.getStorage().entrySet()) { + programListener.onStoragePut(entry.getKey(), entry.getValue()); + } + } + } + repository.updateBatch(accountStates, contractDetails); + } + + @Override + public byte[] getRoot() { + return repository.getRoot(); + } + + @Override + public void loadAccount(byte[] addr, HashMap cacheAccounts, HashMap cacheDetails) { + repository.loadAccount(addr, cacheAccounts, cacheDetails); + } + + @Override + public Repository getSnapshotTo(byte[] root) { + throw new UnsupportedOperationException(); + } + + @Override + public int getStorageSize(byte[] addr) { + return repository.getStorageSize(addr); + } + + @Override + public Set getStorageKeys(byte[] addr) { + return repository.getStorageKeys(addr); + } + + @Override + public Map getStorage(byte[] addr, @Nullable Collection keys) { + return repository.getStorage(addr, keys); + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvoke.java b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvoke.java new file mode 100644 index 0000000..34f9ed8 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvoke.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.invoke; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.db.BlockStoreIfc; + +/** + * @author Roman Mandeleil + * @since 03.06.2014 + */ +public interface ProgramInvoke { + + DataWord getOwnerAddress(); + + DataWord getBalance(); + + DataWord getOriginAddress(); + + DataWord getCallerAddress(); + + DataWord getMinEnergonPrice(); + + DataWord getEnergon(); + + long getEnergonLong(); + + DataWord getCallValue(); + + DataWord getDataSize(); + + DataWord getDataValue(DataWord indexData); + + byte[] getDataCopy(DataWord offsetData, DataWord lengthData); + + DataWord getPrevHash(); + + DataWord getCoinbase(); + + DataWord getTimestamp(); + + DataWord getNumber(); + + DataWord getDifficulty(); + + DataWord getEnergonLimit(); + + boolean byTransaction(); + + boolean byTestingSuite(); + + int getCallDeep(); + + Repository getRepository(); + + BlockStoreIfc getBlockStore(); + + boolean isStaticCall(); +} diff --git a/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactory.java b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactory.java new file mode 100644 index 0000000..a35353c --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.invoke; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.block.Block; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.vm.program.Program; + +import java.math.BigInteger; + +/** + * @author Roman Mandeleil + * @since 19.12.2014 + */ +public interface ProgramInvokeFactory { + + ProgramInvoke createProgramInvoke(Transaction tx, Block block, + Repository repository, BlockStoreIfc blockStore); + + ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord callerAddress, + DataWord inValue, DataWord inGas, + BigInteger balanceInt, byte[] dataIn, + Repository repository, BlockStoreIfc blockStore, + boolean staticCall, boolean byTestingSuite); + + +} diff --git a/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactoryImpl.java b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactoryImpl.java new file mode 100644 index 0000000..1574d3e --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeFactoryImpl.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.invoke; + +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.utils.ByteUtil; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.block.Block; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.transaction.Transaction; +import org.platon.core.vm.program.Program; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; + +import static org.apache.commons.lang3.ArrayUtils.nullToEmpty; + +/** + * @author Roman Mandeleil + * @since 08.06.2014 + */ +@Component("ProgramInvokeFactory") +public class ProgramInvokeFactoryImpl implements ProgramInvokeFactory { + + private static final Logger logger = LoggerFactory.getLogger("VM"); + + // Invocation by the wire tx + @Override + public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository, + BlockStoreIfc blockStore) { + + /*** ADDRESS op ***/ + // YP: Get address of currently executing account. + byte[] address = tx.isContractCreation() ? tx.getContractAddress() : tx.getReceiveAddress(); + + /*** ORIGIN op ***/ + // YP: This is the sender of original transaction; it is never a contract. + byte[] origin = tx.getSender(); + + /*** CALLER op ***/ + // YP: This is the address of the account that is directly responsible for this execution. + byte[] caller = tx.getSender(); + + /*** BALANCE op ***/ + byte[] balance = repository.getBalance(address).toByteArray(); + + /*** GASPRICE op ***/ + BigInteger energonPrice = tx.getEnergonPrice(); + + /*** GAS op ***/ + BigInteger energon = tx.getEnergonLimit(); + + /*** CALLVALUE op ***/ + byte[] callValue = nullToEmpty(tx.getValue().toByteArray()); + + /*** CALLDATALOAD op ***/ + /*** CALLDATACOPY op ***/ + /*** CALLDATASIZE op ***/ + byte[] data = tx.isContractCreation() ? ByteUtil.EMPTY_BYTE_ARRAY : nullToEmpty(tx.getData()); + + /*** PREVHASH op ***/ + byte[] lastHash = block.getBlockHeader().getParentHash(); + + /*** COINBASE op ***/ + //TODO coinbase + byte[] coinbase = null;//block.getCoinbase(); + + /*** TIMESTAMP op ***/ + long timestamp = block.getBlockHeader().getTimestamp(); + + /*** NUMBER op ***/ + long number = block.getBlockHeader().getNumber(); + + /*** DIFFICULTY op ***/ + byte[] difficulty = block.getBlockHeader().getDifficulty().toByteArray(); + + /*** GASLIMIT op ***/ + byte[] gaslimit = block.getBlockHeader().getEnergonCeiling().toByteArray(); + + if (logger.isInfoEnabled()) { + logger.info("Top level call: \n" + + "tx.hash={}\n" + + "address={}\n" + + "origin={}\n" + + "caller={}\n" + + "balance={}\n" + + "gasPrice={}\n" + + "gas={}\n" + + "callValue={}\n" + + "data={}\n" + + "lastHash={}\n" + + "coinbase={}\n" + + "timestamp={}\n" + + "blockNumber={}\n" + + "difficulty={}\n" + + "gaslimit={}\n", + + Hex.toHexString(tx.getHash()), + Hex.toHexString(address), + Hex.toHexString(origin), + Hex.toHexString(caller), + ByteUtil.bytesToBigInteger(balance), + energonPrice, + energon, + ByteUtil.bytesToBigInteger(callValue), + Hex.toHexString(data), + Hex.toHexString(lastHash), + Hex.toHexString(coinbase), + timestamp, + number, + Hex.toHexString(difficulty), + gaslimit); + } + + return new ProgramInvokeImpl(address, origin, caller, balance, energonPrice.toByteArray(), energon.toByteArray(), callValue, data, + lastHash, coinbase, timestamp, number, difficulty, gaslimit, + repository, blockStore); + } + + /** + * This invocation created for contract call contract + */ + @Override + public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord callerAddress, + DataWord inValue, DataWord inGas, + BigInteger balanceInt, byte[] dataIn, + Repository repository, BlockStoreIfc blockStore, + boolean isStaticCall, boolean byTestingSuite) { + + DataWord address = toAddress; + DataWord origin = program.getOriginAddress(); + DataWord caller = callerAddress; + + DataWord balance = DataWord.of(balanceInt.toByteArray()); + DataWord gasPrice = program.getEnergonPrice(); + DataWord gas = inGas; + DataWord callValue = inValue; + + byte[] data = dataIn; + DataWord lastHash = program.getPrevHash(); + DataWord coinbase = program.getCoinbase(); + DataWord timestamp = program.getTimestamp(); + DataWord number = program.getNumber(); + DataWord difficulty = program.getDifficulty(); + DataWord gasLimit = program.getEnergonLimit(); + + if (logger.isInfoEnabled()) { + logger.info("Internal call: \n" + + "address={}\n" + + "origin={}\n" + + "caller={}\n" + + "balance={}\n" + + "gasPrice={}\n" + + "gas={}\n" + + "callValue={}\n" + + "data={}\n" + + "lastHash={}\n" + + "coinbase={}\n" + + "timestamp={}\n" + + "blockNumber={}\n" + + "difficulty={}\n" + + "gaslimit={}\n", + Hex.toHexString(address.getLast20Bytes()), + Hex.toHexString(origin.getLast20Bytes()), + Hex.toHexString(caller.getLast20Bytes()), + balance.toString(), + gasPrice.longValue(), + gas.longValue(), + Hex.toHexString(callValue.getNoLeadZeroesData()), + Hex.toHexString(data), + Hex.toHexString(lastHash.getData()), + Hex.toHexString(coinbase.getLast20Bytes()), + timestamp.longValue(), + number.longValue(), + Hex.toHexString(difficulty.getNoLeadZeroesData()), + gasLimit.bigIntValue()); + } + + return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, + data, lastHash, coinbase, timestamp, number, difficulty, gasLimit, + repository, program.getCallDeep() + 1, blockStore, isStaticCall, byTestingSuite); + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeImpl.java b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeImpl.java new file mode 100644 index 0000000..007e227 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeImpl.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.invoke; + +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.db.BlockStoreIfc; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Map; + +/** + * @author Roman Mandeleil + * @since 03.06.2014 + */ +public class ProgramInvokeImpl implements ProgramInvoke { + + private BlockStoreIfc blockStore; + /** + * TRANSACTION env ** + */ + private final DataWord address; + private final DataWord origin, caller, + balance, energon, energonPrice, callValue; + private final long energonLong; + + byte[] msgData; + + /** + * BLOCK env ** + */ + private final DataWord prevHash, coinbase, timestamp, + number, difficulty, energonLimit; + + private Map storage; + + private final Repository repository; + private boolean byTransaction = true; + private boolean byTestingSuite = false; + private int callDeep = 0; + private boolean isStaticCall = false; + + public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, DataWord balance, + DataWord energonPrice, DataWord energon, DataWord callValue, byte[] msgData, + DataWord lastHash, DataWord coinbase, DataWord timestamp, DataWord number, DataWord + difficulty, + DataWord energonlimit, Repository repository, int callDeep, BlockStoreIfc blockStore, + boolean isStaticCall, boolean byTestingSuite) { + + // Transaction env + this.address = address; + this.origin = origin; + this.caller = caller; + this.balance = balance; + this.energonPrice = energonPrice; + this.energon = energon; + this.energonLong = this.energon.longValueSafe(); + this.callValue = callValue; + this.msgData = msgData; + + // last Block env + this.prevHash = lastHash; + this.coinbase = coinbase; + this.timestamp = timestamp; + this.number = number; + this.difficulty = difficulty; + this.energonLimit = energonlimit; + + this.repository = repository; + this.byTransaction = false; + this.callDeep = callDeep; + this.blockStore = blockStore; + this.isStaticCall = isStaticCall; + this.byTestingSuite = byTestingSuite; + } + + public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, + byte[] energonPrice, byte[] energon, byte[] callValue, byte[] msgData, + byte[] lastHash, byte[] coinbase, long timestamp, long number, byte[] difficulty, + byte[] energonlimit, + Repository repository, BlockStoreIfc blockStore, boolean byTestingSuite) { + this(address, origin, caller, balance, energonPrice, energon, callValue, msgData, lastHash, coinbase, + timestamp, number, difficulty, energonlimit, repository, blockStore); + this.byTestingSuite = byTestingSuite; + } + + + public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, + byte[] energonPrice, byte[] energon, byte[] callValue, byte[] msgData, + byte[] lastHash, byte[] coinbase, long timestamp, long number, byte[] difficulty, + byte[] energonlimit, + Repository repository, BlockStoreIfc blockStore) { + + // Transaction env + this.address = DataWord.of(address); + this.origin = DataWord.of(origin); + this.caller = DataWord.of(caller); + this.balance = DataWord.of(balance); + this.energonPrice = DataWord.of(energonPrice); + this.energon = DataWord.of(energon); + this.energonLong = this.energon.longValueSafe(); + this.callValue = DataWord.of(callValue); + this.msgData = msgData; + + // last Block env + this.prevHash = DataWord.of(lastHash); + this.coinbase = DataWord.of(coinbase); + this.timestamp = DataWord.of(timestamp); + this.number = DataWord.of(number); + this.difficulty = DataWord.of(difficulty); + this.energonLimit = DataWord.of(energonlimit); + + this.repository = repository; + this.blockStore = blockStore; + } + + /* ADDRESS op */ + public DataWord getOwnerAddress() { + return address; + } + + /* BALANCE op */ + public DataWord getBalance() { + return balance; + } + + /* ORIGIN op */ + public DataWord getOriginAddress() { + return origin; + } + + /* CALLER op */ + public DataWord getCallerAddress() { + return caller; + } + + /* energonPRICE op */ + public DataWord getMinEnergonPrice() { + return energonPrice; + } + + /* energon op */ + public DataWord getEnergon() { + return energon; + } + + @Override + public long getEnergonLong() { + return energonLong; + } + + /* CALLVALUE op */ + public DataWord getCallValue() { + return callValue; + } + + /*****************/ + /*** msg data ***/ + /*****************/ + /* NOTE: In the protocol there is no restriction on the maximum message data, + * However msgData here is a byte[] and this can't hold more than 2^32-1 + */ + private static BigInteger MAX_MSG_DATA = BigInteger.valueOf(Integer.MAX_VALUE); + + /* CALLDATALOAD op */ + public DataWord getDataValue(DataWord indexData) { + + BigInteger tempIndex = indexData.value(); + int index = tempIndex.intValue(); // possible overflow is caught below + int size = 32; // maximum datavalue size + + if (msgData == null || index >= msgData.length + || tempIndex.compareTo(MAX_MSG_DATA) == 1) + return DataWord.ZERO; + if (index + size > msgData.length) + size = msgData.length - index; + + byte[] data = new byte[32]; + System.arraycopy(msgData, index, data, 0, size); + return DataWord.of(data); + } + + /* CALLDATASIZE */ + public DataWord getDataSize() { + + if (msgData == null || msgData.length == 0) return DataWord.ZERO; + int size = msgData.length; + return DataWord.of(size); + } + + /* CALLDATACOPY */ + public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { + + int offset = offsetData.intValueSafe(); + int length = lengthData.intValueSafe(); + + byte[] data = new byte[length]; + + if (msgData == null) return data; + if (offset > msgData.length) return data; + if (offset + length > msgData.length) length = msgData.length - offset; + + System.arraycopy(msgData, offset, data, 0, length); + + return data; + } + + + /* PREVHASH op */ + public DataWord getPrevHash() { + return prevHash; + } + + /* COINBASE op */ + public DataWord getCoinbase() { + return coinbase; + } + + /* TIMESTAMP op */ + public DataWord getTimestamp() { + return timestamp; + } + + /* NUMBER op */ + public DataWord getNumber() { + return number; + } + + /* DIFFICULTY op */ + public DataWord getDifficulty() { + return difficulty; + } + + /* energonLIMIT op */ + public DataWord getEnergonLimit() { + return energonLimit; + } + + /* Storage */ + public Map getStorage() { + return storage; + } + + public Repository getRepository() { + return repository; + } + + @Override + public BlockStoreIfc getBlockStore() { + return blockStore; + } + + @Override + public boolean byTransaction() { + return byTransaction; + } + + @Override + public boolean isStaticCall() { + return isStaticCall; + } + + @Override + public boolean byTestingSuite() { + return byTestingSuite; + } + + @Override + public int getCallDeep() { + return this.callDeep; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ProgramInvokeImpl that = (ProgramInvokeImpl) o; + + if (byTestingSuite != that.byTestingSuite) return false; + if (byTransaction != that.byTransaction) return false; + if (address != null ? !address.equals(that.address) : that.address != null) return false; + if (balance != null ? !balance.equals(that.balance) : that.balance != null) return false; + if (callValue != null ? !callValue.equals(that.callValue) : that.callValue != null) return false; + if (caller != null ? !caller.equals(that.caller) : that.caller != null) return false; + if (coinbase != null ? !coinbase.equals(that.coinbase) : that.coinbase != null) return false; + if (difficulty != null ? !difficulty.equals(that.difficulty) : that.difficulty != null) return false; + if (energon != null ? !energon.equals(that.energon) : that.energon != null) return false; + if (energonPrice != null ? !energonPrice.equals(that.energonPrice) : that.energonPrice != null) return false; + if (energonLimit != null ? !energonLimit.equals(that.energonLimit) : that.energonLimit != null) return false; + if (!Arrays.equals(msgData, that.msgData)) return false; + if (number != null ? !number.equals(that.number) : that.number != null) return false; + if (origin != null ? !origin.equals(that.origin) : that.origin != null) return false; + if (prevHash != null ? !prevHash.equals(that.prevHash) : that.prevHash != null) return false; + if (repository != null ? !repository.equals(that.repository) : that.repository != null) return false; + if (storage != null ? !storage.equals(that.storage) : that.storage != null) return false; + if (timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null) return false; + + return true; + } + + @Override + public String toString() { + return "ProgramInvokeImpl{" + + "address=" + address + + ", origin=" + origin + + ", caller=" + caller + + ", balance=" + balance + + ", energon=" + energon + + ", energonPrice=" + energonPrice + + ", callValue=" + callValue + + ", msgData=" + Arrays.toString(msgData) + + ", prevHash=" + prevHash + + ", coinbase=" + coinbase + + ", timestamp=" + timestamp + + ", number=" + number + + ", difficulty=" + difficulty + + ", energonlimit=" + energonLimit + + ", storage=" + storage + + ", repository=" + repository + + ", byTransaction=" + byTransaction + + ", byTestingSuite=" + byTestingSuite + + ", callDeep=" + callDeep + + '}'; + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeMockImpl.java b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeMockImpl.java new file mode 100644 index 0000000..9b538b3 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/invoke/ProgramInvokeMockImpl.java @@ -0,0 +1,232 @@ +package org.platon.core.vm.program.invoke; + +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.core.db.BlockStoreIfc; +import org.platon.core.db.BlockStoreImpl; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; + +/** + * @author Roman Mandeleil + * @since 03.06.2014 + */ +public class ProgramInvokeMockImpl implements ProgramInvoke { + + private byte[] msgData; + + private Repository repository; + private byte[] ownerAddress = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); + private final byte[] contractAddress = Hex.decode("471fd3ad3e9eeadeec4608b92d16ce6b500704cc"); + + // default for most tests. This can be overwritten by the test + private long energonLimit = 1000000; + + public ProgramInvokeMockImpl(byte[] msgDataRaw) { + this(); + this.msgData = msgDataRaw; + } + + public ProgramInvokeMockImpl() { + + +// this.repository = new RepositoryRoot(new HashMapDB()); + this.repository.createAccount(ownerAddress); + + this.repository.createAccount(contractAddress); + this.repository.saveCode(contractAddress, + Hex.decode("385E60076000396000605f556014600054601e60" + + "205463abcddcba6040545b51602001600a525451" + + "6040016014525451606001601e52545160800160" + + "28525460a052546016604860003960166000f260" + + "00603f556103e75660005460005360200235")); + } + + public ProgramInvokeMockImpl(boolean defaults) { + + + } + + /* ADDRESS op */ + public DataWord getOwnerAddress() { + return DataWord.of(ownerAddress); + } + + /* BALANCE op */ + public DataWord getBalance() { + byte[] balance = Hex.decode("0DE0B6B3A7640000"); + return DataWord.of(balance); + } + + /* ORIGIN op */ + public DataWord getOriginAddress() { + + byte[] cowPrivKey = HashUtil.sha3("horse".getBytes()); + byte[] addr = ECKey.fromPrivate(cowPrivKey).getAddress(); + + return DataWord.of(addr); + } + + /* CALLER op */ + public DataWord getCallerAddress() { + + byte[] cowPrivKey = HashUtil.sha3("monkey".getBytes()); + byte[] addr = ECKey.fromPrivate(cowPrivKey).getAddress(); + + return DataWord.of(addr); + } + + /* EnergonPrice op */ + public DataWord getMinEnergonPrice() { + + byte[] minEnergonPrice = Hex.decode("09184e72a000"); + return DataWord.of(minEnergonPrice); + } + + /* Energon op */ + public DataWord getEnergon() { + + return DataWord.of(energonLimit); + } + + @Override + public long getEnergonLong() { + return energonLimit; + } + + public void setEnergon(long energonLimit) { + this.energonLimit = energonLimit; + } + + /* CALLVALUE op */ + public DataWord getCallValue() { + byte[] balance = Hex.decode("0DE0B6B3A7640000"); + return DataWord.of(balance); + } + + /*****************/ + /*** msg data ***/ + /** + * ************* + */ + + /* CALLDATALOAD op */ + public DataWord getDataValue(DataWord indexData) { + + byte[] data = new byte[32]; + + int index = indexData.value().intValue(); + int size = 32; + + if (msgData == null) return DataWord.of(data); + if (index > msgData.length) return DataWord.of(data); + if (index + 32 > msgData.length) size = msgData.length - index; + + System.arraycopy(msgData, index, data, 0, size); + + return DataWord.of(data); + } + + /* CALLDATASIZE */ + public DataWord getDataSize() { + + if (msgData == null || msgData.length == 0) return DataWord.of(new byte[32]); + int size = msgData.length; + return DataWord.of(size); + } + + /* CALLDATACOPY */ + public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { + + int offset = offsetData.value().intValue(); + int length = lengthData.value().intValue(); + + byte[] data = new byte[length]; + + if (msgData == null) return data; + if (offset > msgData.length) return data; + if (offset + length > msgData.length) length = msgData.length - offset; + + System.arraycopy(msgData, offset, data, 0, length); + + return data; + } + + @Override + public DataWord getPrevHash() { + byte[] prevHash = Hex.decode("961CB117ABA86D1E596854015A1483323F18883C2D745B0BC03E87F146D2BB1C"); + return DataWord.of(prevHash); + } + + @Override + public DataWord getCoinbase() { + byte[] coinBase = Hex.decode("E559DE5527492BCB42EC68D07DF0742A98EC3F1E"); + return DataWord.of(coinBase); + } + + @Override + public DataWord getTimestamp() { + long timestamp = 1401421348; + return DataWord.of(timestamp); + } + + @Override + public DataWord getNumber() { + long number = 33; + return DataWord.of(number); + } + + @Override + public DataWord getDifficulty() { + byte[] difficulty = Hex.decode("3ED290"); + return DataWord.of(difficulty); + } + + @Override + public DataWord getEnergonLimit() { + return DataWord.of(energonLimit); + } + + public void setEnergonLimit(long energonLimit) { + this.energonLimit = energonLimit; + } + + public void setOwnerAddress(byte[] ownerAddress) { + this.ownerAddress = ownerAddress; + } + + @Override + public boolean byTransaction() { + return true; + } + + @Override + public boolean isStaticCall() { + return false; + } + + @Override + public boolean byTestingSuite() { + return false; + } + + @Override + public Repository getRepository() { + return this.repository; + } + + @Override + public BlockStoreIfc getBlockStore() { + return new BlockStoreImpl(); + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + @Override + public int getCallDeep() { + return 0; + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/listener/CompositeProgramListener.java b/core/src/main/java/org/platon/core/vm/program/listener/CompositeProgramListener.java new file mode 100644 index 0000000..1f1b637 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/listener/CompositeProgramListener.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.listener; + +import org.platon.common.wrapper.DataWord; + +import java.util.ArrayList; +import java.util.List; + + +public class CompositeProgramListener implements ProgramListener { + + private List listeners = new ArrayList<>(); + + @Override + public void onMemoryExtend(int delta) { + for (ProgramListener listener : listeners) { + listener.onMemoryExtend(delta); + } + } + + @Override + public void onMemoryWrite(int address, byte[] data, int size) { + for (ProgramListener listener : listeners) { + listener.onMemoryWrite(address, data, size); + } + } + + @Override + public void onStackPop() { + for (ProgramListener listener : listeners) { + listener.onStackPop(); + } + } + + @Override + public void onStackPush(DataWord value) { + for (ProgramListener listener : listeners) { + listener.onStackPush(value); + } + } + + @Override + public void onStackSwap(int from, int to) { + for (ProgramListener listener : listeners) { + listener.onStackSwap(from, to); + } + } + + @Override + public void onStoragePut(DataWord key, DataWord value) { + for (ProgramListener listener : listeners) { + listener.onStoragePut(key, value); + } + } + + @Override + public void onStorageClear() { + for (ProgramListener listener : listeners) { + listener.onStorageClear(); + } + } + + public void addListener(ProgramListener listener) { + listeners.add(listener); + } + + public boolean isEmpty() { + return listeners.isEmpty(); + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/listener/ProgramListener.java b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListener.java new file mode 100644 index 0000000..89775bc --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.listener; + + +import org.platon.common.wrapper.DataWord; + +public interface ProgramListener { + + void onMemoryExtend(int delta); + + void onMemoryWrite(int address, byte[] data, int size); + + void onStackPop(); + + void onStackPush(DataWord value); + + void onStackSwap(int from, int to); + + void onStoragePut(DataWord key, DataWord value); + + void onStorageClear(); +} \ No newline at end of file diff --git a/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAdaptor.java b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAdaptor.java new file mode 100644 index 0000000..cc2cba8 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAdaptor.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.listener; + +import org.platon.common.wrapper.DataWord; + +public class ProgramListenerAdaptor implements ProgramListener { + + @Override + public void onMemoryExtend(int delta) { + + } + + @Override + public void onMemoryWrite(int address, byte[] data, int size) { + + } + + @Override + public void onStackPop() { + + } + + @Override + public void onStackPush(DataWord value) { + + } + + @Override + public void onStackSwap(int from, int to) { + + } + + @Override + public void onStoragePut(DataWord key, DataWord value) { + + } + + @Override + public void onStorageClear() { + + } +} diff --git a/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAware.java b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAware.java new file mode 100644 index 0000000..a4f2ac5 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/listener/ProgramListenerAware.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.listener; + +public interface ProgramListenerAware { + + void setProgramListener(ProgramListener listener); +} diff --git a/core/src/main/java/org/platon/core/vm/program/listener/ProgramStorageChangeListener.java b/core/src/main/java/org/platon/core/vm/program/listener/ProgramStorageChangeListener.java new file mode 100644 index 0000000..732d8ce --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/program/listener/ProgramStorageChangeListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.program.listener; + +import org.platon.common.wrapper.DataWord; + +import java.util.HashMap; +import java.util.Map; + +public class ProgramStorageChangeListener extends ProgramListenerAdaptor { + + private Map diff = new HashMap<>(); + + @Override + public void onStoragePut(DataWord key, DataWord value) { + diff.put(key, value); + } + + @Override + public void onStorageClear() { + // TODO: ... + } + + public Map getDiff() { + return new HashMap<>(diff); + } + + public void merge(Map diff) { + this.diff.putAll(diff); + } +} diff --git a/core/src/main/java/org/platon/core/vm/trace/Op.java b/core/src/main/java/org/platon/core/vm/trace/Op.java new file mode 100644 index 0000000..3148571 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/trace/Op.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.trace; + +import org.platon.core.vm.OpCode; + +import java.math.BigInteger; + +public class Op { + + private OpCode code; + private int deep; + private int pc; + private BigInteger gas; + private OpActions actions; + + public OpCode getCode() { + return code; + } + + public void setCode(OpCode code) { + this.code = code; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public int getPc() { + return pc; + } + + public void setPc(int pc) { + this.pc = pc; + } + + public BigInteger getGas() { + return gas; + } + + public void setGas(BigInteger gas) { + this.gas = gas; + } + + public OpActions getActions() { + return actions; + } + + public void setActions(OpActions actions) { + this.actions = actions; + } +} diff --git a/core/src/main/java/org/platon/core/vm/trace/OpActions.java b/core/src/main/java/org/platon/core/vm/trace/OpActions.java new file mode 100644 index 0000000..ec5d230 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/trace/OpActions.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.trace; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.wrapper.DataWord; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OpActions { + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Action { + + public enum Name { + pop, + push, + swap, + extend, + write, + put, + remove, + clear; + } + + private Name name; + private Map params; + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + Action addParam(String name, Object value) { + if (value != null) { + if (params == null) { + params = new HashMap<>(); + } + params.put(name, value.toString()); + } + return this; + } + } + + private List stack = new ArrayList<>(); + private List memory = new ArrayList<>(); + private List storage = new ArrayList<>(); + + public List getStack() { + return stack; + } + + public void setStack(List stack) { + this.stack = stack; + } + + public List getMemory() { + return memory; + } + + public void setMemory(List memory) { + this.memory = memory; + } + + public List getStorage() { + return storage; + } + + public void setStorage(List storage) { + this.storage = storage; + } + + private static Action addAction(List container, Action.Name name) { + Action action = new Action(); + action.setName(name); + + container.add(action); + + return action; + } + + public Action addStackPop() { + return addAction(stack, Action.Name.pop); + } + + public Action addStackPush(DataWord value) { + return addAction(stack, Action.Name.push) + .addParam("value", value); + } + + public Action addStackSwap(int from, int to) { + return addAction(stack, Action.Name.swap) + .addParam("from", from) + .addParam("to", to); + } + + public Action addMemoryExtend(long delta) { + return addAction(memory, Action.Name.extend) + .addParam("delta", delta); + } + + public Action addMemoryWrite(int address, byte[] data, int size) { + return addAction(memory, Action.Name.write) + .addParam("address", address) + .addParam("data", Hex.toHexString(data).substring(0, size)); + } + + public Action addStoragePut(DataWord key, DataWord value) { + return addAction(storage, Action.Name.put) + .addParam("key", key) + .addParam("value", value); + } + + public Action addStorageRemove(DataWord key) { + return addAction(storage, Action.Name.remove) + .addParam("key", key); + } + + public Action addStorageClear() { + return addAction(storage, Action.Name.clear); + } +} diff --git a/core/src/main/java/org/platon/core/vm/trace/ProgramTrace.java b/core/src/main/java/org/platon/core/vm/trace/ProgramTrace.java new file mode 100644 index 0000000..13136b4 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/trace/ProgramTrace.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.trace; + +import org.bouncycastle.util.encoders.Hex; +import org.platon.common.wrapper.DataWord; +import org.platon.core.config.CoreConfig; +import org.platon.core.vm.OpCode; +import org.platon.core.vm.program.invoke.ProgramInvoke; + +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.format; + +public class ProgramTrace { + + private List ops = new ArrayList<>(); + private String result; + private String error; + private String contractAddress; + + public ProgramTrace() { + this(null, null); + } + + public ProgramTrace(CoreConfig config, ProgramInvoke programInvoke) { + if (programInvoke != null && config.vmTrace()) { + contractAddress = Hex.toHexString(programInvoke.getOwnerAddress().getLast20Bytes()); + } + } + + public List getOps() { + return ops; + } + + public void setOps(List ops) { + this.ops = ops; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getContractAddress() { + return contractAddress; + } + + public void setContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + } + + public ProgramTrace result(byte[] result) { + setResult(Hex.toHexString(result)); + return this; + } + + public ProgramTrace error(Exception error) { + setError(error == null ? "" : format("%s: %s", error.getClass(), error.getMessage())); + return this; + } + + public Op addOp(byte code, int pc, int deep, DataWord gas, OpActions actions) { + Op op = new Op(); + op.setActions(actions); + op.setCode(OpCode.code(code)); + op.setDeep(deep); + op.setGas(gas.value()); + op.setPc(pc); + + ops.add(op); + + return op; + } + + /** + * Used for merging sub calls execution. + */ + public void merge(ProgramTrace programTrace) { + this.ops.addAll(programTrace.ops); + } + + public String asJsonString(boolean formatted) { + return Serializers.serializeFieldsOnly(this, formatted); + } + + @Override + public String toString() { + return asJsonString(true); + } +} diff --git a/core/src/main/java/org/platon/core/vm/trace/ProgramTraceListener.java b/core/src/main/java/org/platon/core/vm/trace/ProgramTraceListener.java new file mode 100644 index 0000000..af4668d --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/trace/ProgramTraceListener.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.trace; + + +import org.platon.common.wrapper.DataWord; +import org.platon.core.vm.program.listener.ProgramListenerAdaptor; + +public class ProgramTraceListener extends ProgramListenerAdaptor { + + private final boolean enabled; + private OpActions actions = new OpActions(); + + public ProgramTraceListener(boolean enabled) { + this.enabled = enabled; + } + + @Override + public void onMemoryExtend(int delta) { + if (enabled) actions.addMemoryExtend(delta); + } + + @Override + public void onMemoryWrite(int address, byte[] data, int size) { + if (enabled) actions.addMemoryWrite(address, data, size); + } + + @Override + public void onStackPop() { + if (enabled) actions.addStackPop(); + } + + @Override + public void onStackPush(DataWord value) { + if (enabled) actions.addStackPush(value); + } + + @Override + public void onStackSwap(int from, int to) { + if (enabled) actions.addStackSwap(from, to); + } + + @Override + public void onStoragePut(DataWord key, DataWord value) { + if (enabled) { + if (value.equals(DataWord.ZERO)) { + actions.addStorageRemove(key); + } else { + actions.addStoragePut(key, value); + } + } + } + + @Override + public void onStorageClear() { + if (enabled) actions.addStorageClear(); + } + + public OpActions resetActions() { + OpActions actions = this.actions; + this.actions = new OpActions(); + return actions; + } +} diff --git a/core/src/main/java/org/platon/core/vm/trace/Serializers.java b/core/src/main/java/org/platon/core/vm/trace/Serializers.java new file mode 100644 index 0000000..8da6ed8 --- /dev/null +++ b/core/src/main/java/org/platon/core/vm/trace/Serializers.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.vm.trace; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker; +import org.platon.common.wrapper.DataWord; +import org.platon.core.vm.OpCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.IOException; + +public final class Serializers { + + private static final Logger LOGGER = LoggerFactory.getLogger("vmtrace"); + + public static class DataWordSerializer extends JsonSerializer { + + @Override + public void serialize(DataWord gas, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(gas.value().toString()); + } + } + + public static class ByteArraySerializer extends JsonSerializer { + + @Override + public void serialize(byte[] memory, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(Hex.toHexString(memory)); + } + } + + public static class OpCodeSerializer extends JsonSerializer { + + @Override + public void serialize(Byte op, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(OpCode.code(op).name()); + } + } + + + public static String serializeFieldsOnly(Object value, boolean pretty) { + try { + ObjectMapper mapper = createMapper(pretty); + mapper.setVisibilityChecker(fieldsOnlyVisibilityChecker(mapper)); + + return mapper.writeValueAsString(value); + } catch (Exception e) { + LOGGER.error("JSON serialization error: ", e); + return "{}"; + } + } + + private static VisibilityChecker fieldsOnlyVisibilityChecker(ObjectMapper mapper) { + return mapper.getSerializationConfig().getDefaultVisibilityChecker() + .withFieldVisibility(JsonAutoDetect.Visibility.ANY) + .withGetterVisibility(JsonAutoDetect.Visibility.NONE) + .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE); + } + + public static ObjectMapper createMapper(boolean pretty) { + ObjectMapper mapper = new ObjectMapper(); + if (pretty) { + mapper.enable(SerializationFeature.INDENT_OUTPUT); + } + return mapper; + } +} diff --git a/core/src/main/resources/config/genesis.json b/core/src/main/resources/config/genesis.json new file mode 100644 index 0000000..fdf6cce --- /dev/null +++ b/core/src/main/resources/config/genesis.json @@ -0,0 +1,16 @@ +{ + "config": { + "consensus": "Noproof", + "energonLimit": "1388" + }, + "accounts": { + "5f2ae1c60a3038956cf3355960cb211de78bab50": { + "balance": "10000000000000000000000" + } + }, + "autor": "5f2ae1c60a3038956cf3355960cb211de78bab50", + "coinbase" : "5f2ae1c60a3038956cf3355960cb211de78bab50", + "timestamp": "1533872936173", + "parentHash": "0000000000000000000000000000000000000000000000000000000000000000", + "energonLimit": "1388" +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/AccountTest.java b/core/src/test/java/org/platon/core/AccountTest.java new file mode 100644 index 0000000..1d85db9 --- /dev/null +++ b/core/src/test/java/org/platon/core/AccountTest.java @@ -0,0 +1,48 @@ +package org.platon.core; + +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.*; + +public class AccountTest { + + final BigInteger balance = new BigInteger("10000"); + final byte[] storageRoot = "storageRoot".getBytes(); + final byte[] binHash = "binHash".getBytes(); + final byte[] permissionRoot = "permissionRoot".getBytes(); + + @Test + public void getEncoded() { + Account account = this.init(); + Account account1 = new Account(account.getEncoded()); + Assert.assertArrayEquals(account.getStorageRoot(),account1.getStorageRoot()); + } + + @Test + public void addBalance() { + Account account = this.init(); + BigInteger addBalance = new BigInteger("-1"); + account.addBalance(addBalance); + Assert.assertEquals(account.getBalance().toString(),balance.add(addBalance).toString()); + } + + @Test + public void isEmpty() { + Account account = this.init(); + Assert.assertFalse(account.isEmpty()); + } + + @Test + public void untouch() { + Account account = this.init(); + account.untouch(); + Assert.assertFalse(account.isDirty()); + } + + private Account init(){ + return new Account(balance,storageRoot,binHash,permissionRoot); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/block/BlockHeaderTest.java b/core/src/test/java/org/platon/core/block/BlockHeaderTest.java new file mode 100644 index 0000000..aa25009 --- /dev/null +++ b/core/src/test/java/org/platon/core/block/BlockHeaderTest.java @@ -0,0 +1,63 @@ +//package org.platon.core.block; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.platon.core.chain.Chain; +//import org.platon.core.config.DefaultConfig; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.test.context.ContextConfiguration; +//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +// +///** +// * Created by alliswell on 2018/8/8. +// */ +//@RunWith(SpringJUnit4ClassRunner.class) +//@ContextConfiguration(classes = DefaultConfig.class) +//public class BlockHeaderTest { +// +// @Autowired +// Chain chain; +// +// private Block block; +// +// private BlockHeader blockHeader; +// +// @Before +// public void setUp() throws Exception { +// block = chain.getBlock(0); +// blockHeader = block.info(); +// } +// +// @Test +// public void testParse() { +// +// byte[] headerBytes = block.info().encode(); +// blockHeader = new BlockHeader(headerBytes); +// +// Assert.assertEquals(blockHeader, block.info()); +// } +// +// @Test +// public void testPropagatedBy() { +// BlockHeader sonHeader = new BlockHeader(); +// sonHeader.propagatedBy(blockHeader); +// +// Assert.assertEquals(blockHeader.getNumber()+1, sonHeader.getNumber()); +// } +// +// @Test +// public void testSetRoots() { +// BlockHeader sonHeader = new BlockHeader(); +// sonHeader.setRoots(blockHeader.getStateRoot(),blockHeader.getPermissionRoot(), +// blockHeader.getDposRoot(), blockHeader.getTransactionRoot(), blockHeader.getTransferRoot(), +// blockHeader.getVotingRoot(), blockHeader.getReceiptRoot()); +// } +// +// @After +// public void tearDown() throws Exception { +// System.out.println("BlockHeader test OK!"); +// } +//} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/block/BlockPoolTest.java b/core/src/test/java/org/platon/core/block/BlockPoolTest.java new file mode 100644 index 0000000..08561ab --- /dev/null +++ b/core/src/test/java/org/platon/core/block/BlockPoolTest.java @@ -0,0 +1,80 @@ +//package org.platon.core.block; +// +//import org.bouncycastle.util.encoders.Hex; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.platon.core.chain.Chain; +//import org.platon.core.config.DefaultConfig; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.test.context.ContextConfiguration; +//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +// +//@RunWith(SpringJUnit4ClassRunner.class) +//@ContextConfiguration(classes = DefaultConfig.class) +//public class BlockPoolTest { +// +// @Autowired +// Chain chain; +// +// private BlockPool pool = new BlockPool(100); +// +// private Block genesisBlock; +// +// @Before +// public void setUp() throws Exception { +// genesisBlock = chain.genesisBlock(); +// } +// +// @Test +// public void injectRaw() { +// pool.clear(); +// Block block = new Block(); +// block.populateFromParent(genesisBlock.info()); +// pool.injectRaw(block); +// } +// +// @Test +// public void pollRaw() { +// injectRaw(); +// Block block = pool.pollRaw(); +// Assert.assertNotNull(block); +// } +// +// @Test +// public void injectValid() { +// pool.clear(); +// Block block = new Block(); +// block.populateFromParent(genesisBlock.info()); +// pool.injectValid(block); +// } +// +// @Test +// public void injectFork() { +// pool.clear(); +// Block block = new Block(); +// block.populateFromParent(genesisBlock.info()); +// pool.injectFork(block); +// } +// +// @Test +// public void pollValid() { +// injectValid(); +// Block block = pool.pollValid(); +// Assert.assertNotNull(block); +// } +// +// @Test +// public void pollFork() { +// injectFork(); +// Block block = pool.pollFork(); +// Assert.assertNotNull(block); +// } +// +// @Test +// public void collectSignatures() { +// byte[] sig = Hex.decode("0a791201011a2166473538557a77664b7a41565877594e614e55766b4c534550"); +// pool.collectSignatures(genesisBlock.info().getHash(), sig); +// } +//} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/block/BlockTest.java b/core/src/test/java/org/platon/core/block/BlockTest.java new file mode 100644 index 0000000..d00bbb9 --- /dev/null +++ b/core/src/test/java/org/platon/core/block/BlockTest.java @@ -0,0 +1,99 @@ +//package org.platon.core.block; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.platon.common.utils.Numeric; +//import org.platon.core.chain.Chain; +//import org.platon.core.config.DefaultConfig; +//import org.platon.core.state.State; +//import org.platon.core.transaction.TransactionPool; +//import org.platon.core.transaction.TransactionPoolTest; +//import org.platon.core.transaction.TransactionReceipt; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.test.context.ContextConfiguration; +//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +// +//import java.math.BigInteger; +//import java.util.ArrayList; +// +///** test Block class +// * Created by alliswell on 2018/8/8. +// */ +// +//@RunWith(SpringJUnit4ClassRunner.class) +//@ContextConfiguration(classes = DefaultConfig.class) +//public class BlockTest { +// +// @Autowired +// Chain chain; +// +// private byte[] blockHash = "".getBytes(); +// private byte[] address = Numeric.hexStringToByteArray("0xa3ae7747a0690701cc84b453524fa7c99afcd8ac"); +// +// private Block block; +// +// @Before +// public void setUp() throws Exception { +// block = chain.getBlock(0); +// } +// +// @After +// public void tearDown() throws Exception { +// System.out.println("Block test OK!"); +// } +// +// @Test +// public void testGetBalance() { +// +// BigInteger balance = block.getBalance(address); +// System.out.println("[fG58UzwfKzAVXwYNaNUvkLSEPqYSkR25o]'s balance = " + balance.toString()); +// } +// +// @Test +// public void testInfo() { +// BlockHeader header = block.info(); +// Assert.assertNotNull(header); +// } +// +// @Test +// public void testGetState() { +// State state = block.getState(); +// BigInteger balance = state.balance(address); +// System.out.println("balance = " + balance); +// } +// +// @Test +// public void testPopulateFromParent() { +// Block sonBlock = new Block(); +// sonBlock.populateFromParent(block.info()); +// } +// +// @Test +// public void testBuildFromTransactionPool() { +// TransactionPool pool = TransactionPool.getInstance(); +// pool.inject(TransactionPoolTest.getTx1()); +// pool.inject(TransactionPoolTest.getTx2()); +// +// Block sonBlock = new Block(); +// sonBlock.populateFromParent(block.info()); +// ArrayList receipts = sonBlock.buildFromTransactionPool(chain, pool); +// Assert.assertTrue(receipts.size() > 0); +// } +// +// @Test +// public void testSeal() { +// TransactionPool pool = TransactionPool.getInstance(); +// pool.inject(TransactionPoolTest.getTx1()); +// pool.inject(TransactionPoolTest.getTx2()); +// +// Block sonBlock = new Block(); +// sonBlock.populateFromParent(block.info()); +// ArrayList receipts = sonBlock.buildFromTransactionPool(chain, pool); +// +// sonBlock.seal(chain, new byte[0]); +// Assert.assertNotNull(sonBlock.info()); +// } +//} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/config/CoreConfigTest.java b/core/src/test/java/org/platon/core/config/CoreConfigTest.java new file mode 100644 index 0000000..befd64d --- /dev/null +++ b/core/src/test/java/org/platon/core/config/CoreConfigTest.java @@ -0,0 +1,34 @@ +package org.platon.core.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +@Ignore +public class CoreConfigTest { + + @Test + public void test01(){ + System.out.println("work.dir:" + System.getProperty("user.dir")); + CoreConfig coreConfig = CoreConfig.getInstance(); + System.out.println("database.dir:" + coreConfig.databaseDir()); + + // tips: for override. + Map override = new HashMap<>(); + override.put("database.dir", "otherdir"); + coreConfig.overrideParams(override); + Assert.assertEquals("otherdir",coreConfig.databaseDir()); + + // tips: + Config cliConf = ConfigFactory.parseString("a={a1=1,a2=2}"); + coreConfig.overrideParams(cliConf); + String a1 = coreConfig.getConfig().getString("a.a1"); + System.out.println(a1); + Assert.assertEquals("1",a1); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/config/DefaultConfigTest.java b/core/src/test/java/org/platon/core/config/DefaultConfigTest.java new file mode 100644 index 0000000..b9023dd --- /dev/null +++ b/core/src/test/java/org/platon/core/config/DefaultConfigTest.java @@ -0,0 +1,74 @@ +package org.platon.core.config; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.core.read.ListAppender; +import org.junit.Assert; +import org.junit.Test; +import org.platon.common.AppenderName; +import org.platon.core.rpc.ProtoRpcImpl; +import org.platon.core.rpc.ProtoRpc; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; + +import java.util.concurrent.Executors; + +public class DefaultConfigTest { + + public static class TestConfig{ + + @Bean + public ProtoRpc protoRpc(){ + return new ProtoRpcImpl(); + } + + } + + @Test + public void testConstruction() throws InterruptedException { + + + + ListAppender memoryAppender = new ListAppender<>(); + memoryAppender.start(); + Logger logger = (Logger) LoggerFactory.getLogger(AppenderName.APPENDER_PLATIN); + try { + logger.setLevel(Level.DEBUG); + logger.addAppender(memoryAppender); + + new DefaultConfig(); + + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + throw new IllegalStateException("unit test throw exception"); + } + }); + Thread.sleep(500); + ILoggingEvent first = memoryAppender.list.get(0); + Assert.assertEquals("Uncaught exception", first.getMessage()); + + IThrowableProxy cause = first.getThrowableProxy(); + System.out.println(cause.getMessage()); + Assert.assertEquals("unit test throw exception",cause.getMessage()); + } finally { + memoryAppender.stop(); + logger.detachAppender(memoryAppender); + } + } + + @Test + public void testDefaultInject(){ + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DefaultConfig.class,TestConfig.class); + DefaultConfig config = context.getBean(DefaultConfig.class); + TestConfig testConfig = context.getBean(TestConfig.class); + ProtoRpc protoRpc = context.getBean(ProtoRpcImpl.class); + ProtoRpc protoRpc1 = testConfig.protoRpc(); + Assert.assertNotEquals(protoRpc,protoRpc1); + Assert.assertNotNull(config); + Assert.assertNotNull(config.getAppCtx()); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/db/RepositoryTest.java b/core/src/test/java/org/platon/core/db/RepositoryTest.java new file mode 100644 index 0000000..71c9ee7 --- /dev/null +++ b/core/src/test/java/org/platon/core/db/RepositoryTest.java @@ -0,0 +1,928 @@ +package org.platon.core.db; + +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.platon.common.wrapper.DataWord; +import org.platon.core.Repository; +import org.platon.storage.datasource.inmemory.HashMapDB; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RepositoryTest { + + + @Test + public void test1() { + + RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); + + byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); + byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); + + repository.createAccount(cow); + repository.createAccount(horse); + repository.addBalance(cow, BigInteger.TEN); + repository.addBalance(horse, BigInteger.ONE); + assertEquals(BigInteger.TEN, repository.getBalance(cow)); + assertEquals(BigInteger.ONE, repository.getBalance(horse)); + + System.out.println(repository.getTrieDump()); + repository.close(); + } + + + @Test + public void test3() { + // db source + RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); + + byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); + byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); + + byte[] cowCode = Hex.decode("A1A2A3"); + byte[] horseCode = Hex.decode("B1B2B3"); + + repository.saveCode(cow, cowCode); + repository.saveCode(horse, horseCode); + + assertArrayEquals(cowCode, repository.getCode(cow)); + assertArrayEquals(horseCode, repository.getCode(horse)); + System.out.println(repository.getTrieDump()); + repository.close(); + } + + @Test + public void test4() { + + RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); + Repository track = repository.startTracking(); + + byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); + byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); + + byte[] cowKey = Hex.decode("A1A2A3"); + byte[] cowValue = Hex.decode("A4A5A6"); + + byte[] horseKey = Hex.decode("B1B2B3"); + byte[] horseValue = Hex.decode("B4B5B6"); + + track.addStorageRow(cow, DataWord.of(cowKey), DataWord.of(cowValue)); + track.addStorageRow(horse, DataWord.of(horseKey), DataWord.of(horseValue)); + track.commit(); + + assertEquals(DataWord.of(cowValue), repository.getStorageValue(cow, DataWord.of(cowKey))); + assertEquals(DataWord.of(horseValue), repository.getStorageValue(horse, DataWord.of(horseKey))); + + repository.close(); + } + + @Test + public void test6() { + + RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); + Repository track = repository.startTracking(); + + byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); + byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); + + track.addBalance(cow,BigInteger.TEN); + track.addBalance(cow,BigInteger.TEN); + track.addBalance(cow,BigInteger.TEN); + track.addBalance(cow,BigInteger.TEN); + + + track.addBalance(horse,BigInteger.ONE); + + assertEquals(BigInteger.TEN.multiply(BigInteger.valueOf(4)), track.getBalance(cow)); + assertEquals(BigInteger.ONE, track.getBalance(horse)); + + track.rollback(); + + assertEquals(BigInteger.TEN.multiply(BigInteger.valueOf(4)), track.getBalance(cow)); + assertEquals(BigInteger.ONE, track.getBalance(horse)); + + repository.close(); + } +// +// @Test +// public void test7() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// track.addBalance(cow, BigInteger.TEN); +// track.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track.getBalance(cow)); +// assertEquals(BigInteger.ONE, track.getBalance(horse)); +// +// track.commit(); +// +// assertEquals(BigInteger.TEN, repository.getBalance(cow)); +// assertEquals(BigInteger.ONE, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// +// @Test +// public void test8() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// track.addBalance(cow, BigInteger.TEN); +// track.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track.getBalance(cow)); +// assertEquals(BigInteger.ONE, track.getBalance(horse)); +// +// track.rollback(); +// +// assertEquals(BigInteger.ZERO, repository.getBalance(cow)); +// assertEquals(BigInteger.ZERO, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// @Test +// public void test7_1() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track1 = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// track1.addBalance(cow, BigInteger.TEN); +// track1.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track1.getBalance(cow)); +// assertEquals(BigInteger.ONE, track1.getBalance(horse)); +// +// Repository track2 = track1.startTracking(); +// +// assertEquals(BigInteger.TEN, track2.getBalance(cow)); +// assertEquals(BigInteger.ONE, track2.getBalance(horse)); +// +// track2.addBalance(cow, BigInteger.TEN); +// track2.addBalance(cow, BigInteger.TEN); +// track2.addBalance(cow, BigInteger.TEN); +// +// track2.commit(); +// +// track1.commit(); +// +// assertEquals(new BigInteger("40"), repository.getBalance(cow)); +// assertEquals(BigInteger.ONE, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// @Test +// public void test7_2() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track1 = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// track1.addBalance(cow, BigInteger.TEN); +// track1.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track1.getBalance(cow)); +// assertEquals(BigInteger.ONE, track1.getBalance(horse)); +// +// Repository track2 = track1.startTracking(); +// +// assertEquals(BigInteger.TEN, track2.getBalance(cow)); +// assertEquals(BigInteger.ONE, track2.getBalance(horse)); +// +// track2.addBalance(cow, BigInteger.TEN); +// track2.addBalance(cow, BigInteger.TEN); +// track2.addBalance(cow, BigInteger.TEN); +// +// track2.commit(); +// +// track1.rollback(); +// +// assertEquals(BigInteger.ZERO, repository.getBalance(cow)); +// assertEquals(BigInteger.ZERO, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// +// @Test +// public void test9() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// DataWord cowKey = DataWord.of(Hex.decode("A1A2A3")); +// DataWord cowValue = DataWord.of(Hex.decode("A4A5A6")); +// +// DataWord horseKey = DataWord.of(Hex.decode("B1B2B3")); +// DataWord horseValue = DataWord.of(Hex.decode("B4B5B6")); +// +// track.addStorageRow(cow, cowKey, cowValue); +// track.addStorageRow(horse, horseKey, horseValue); +// +// assertEquals(cowValue, track.getStorageValue(cow, cowKey)); +// assertEquals(horseValue, track.getStorageValue(horse, horseKey)); +// +// track.commit(); +// +// assertEquals(cowValue, repository.getStorageValue(cow, cowKey)); +// assertEquals(horseValue, repository.getStorageValue(horse, horseKey)); +// +// repository.close(); +// } +// +// @Test +// public void test10() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// DataWord cowKey = DataWord.of(Hex.decode("A1A2A3")); +// DataWord cowValue = DataWord.of(Hex.decode("A4A5A6")); +// +// DataWord horseKey = DataWord.of(Hex.decode("B1B2B3")); +// DataWord horseValue = DataWord.of(Hex.decode("B4B5B6")); +// +// track.addStorageRow(cow, cowKey, cowValue); +// track.addStorageRow(horse, horseKey, horseValue); +// +// assertEquals(cowValue, track.getStorageValue(cow, cowKey)); +// assertEquals(horseValue, track.getStorageValue(horse, horseKey)); +// +// track.rollback(); +// +// assertEquals(null, repository.getStorageValue(cow, cowKey)); +// assertEquals(null, repository.getStorageValue(horse, horseKey)); +// +// repository.close(); +// } +// +// +// @Test +// public void test11() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowCode = Hex.decode("A1A2A3"); +// byte[] horseCode = Hex.decode("B1B2B3"); +// +// track.saveCode(cow, cowCode); +// track.saveCode(horse, horseCode); +// +// assertArrayEquals(cowCode, track.getCode(cow)); +// assertArrayEquals(horseCode, track.getCode(horse)); +// +// track.commit(); +// +// assertArrayEquals(cowCode, repository.getCode(cow)); +// assertArrayEquals(horseCode, repository.getCode(horse)); +// +// repository.close(); +// } +// +// +// @Test +// public void test12() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowCode = Hex.decode("A1A2A3"); +// byte[] horseCode = Hex.decode("B1B2B3"); +// +// track.saveCode(cow, cowCode); +// track.saveCode(horse, horseCode); +// +// assertArrayEquals(cowCode, track.getCode(cow)); +// assertArrayEquals(horseCode, track.getCode(horse)); +// +// track.rollback(); +// +// assertArrayEquals(EMPTY_BYTE_ARRAY, repository.getCode(cow)); +// assertArrayEquals(EMPTY_BYTE_ARRAY, repository.getCode(horse)); +// +// repository.close(); +// } +// +// @Test // Let's upload genesis pre-mine just like in the real world +// public void test13() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// Genesis genesis = (Genesis)Genesis.getInstance(); +// Genesis.populateRepository(track, genesis); +// +// track.commit(); +// +// assertArrayEquals(Genesis.getInstance().getStateRoot(), repository.getRoot()); +// +// repository.close(); +// } +// +// +// @Test +// public void test14() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// final BigInteger ELEVEN = BigInteger.TEN.add(BigInteger.ONE); +// +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// track1.addBalance(cow, BigInteger.TEN); +// track1.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track1.getBalance(cow)); +// assertEquals(BigInteger.ONE, track1.getBalance(horse)); +// +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addBalance(cow, BigInteger.ONE); +// track2.addBalance(horse, BigInteger.TEN); +// +// assertEquals(ELEVEN, track2.getBalance(cow)); +// assertEquals(ELEVEN, track2.getBalance(horse)); +// +// track2.commit(); +// track1.commit(); +// +// assertEquals(ELEVEN, repository.getBalance(cow)); +// assertEquals(ELEVEN, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// +// @Test +// public void test15() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// final BigInteger ELEVEN = BigInteger.TEN.add(BigInteger.ONE); +// +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// track1.addBalance(cow, BigInteger.TEN); +// track1.addBalance(horse, BigInteger.ONE); +// +// assertEquals(BigInteger.TEN, track1.getBalance(cow)); +// assertEquals(BigInteger.ONE, track1.getBalance(horse)); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addBalance(cow, BigInteger.ONE); +// track2.addBalance(horse, BigInteger.TEN); +// +// assertEquals(ELEVEN, track2.getBalance(cow)); +// assertEquals(ELEVEN, track2.getBalance(horse)); +// +// track2.rollback(); +// track1.commit(); +// +// assertEquals(BigInteger.TEN, repository.getBalance(cow)); +// assertEquals(BigInteger.ONE, repository.getBalance(horse)); +// +// repository.close(); +// } +// +// @Test +// public void test16() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// byte[] horseKey1 = "key-h-1".getBytes(); +// byte[] horseValue1 = "val-h-1".getBytes(); +// +// byte[] cowKey2 = "key-c-2".getBytes(); +// byte[] cowValue2 = "val-c-2".getBytes(); +// +// byte[] horseKey2 = "key-h-2".getBytes(); +// byte[] horseValue2 = "val-h-2".getBytes(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// track1.addStorageRow(cow, DataWord.of(cowKey1), DataWord.of(cowValue1)); +// track1.addStorageRow(horse, DataWord.of(horseKey1), DataWord.of(horseValue1)); +// +// assertEquals(DataWord.of(cowValue1), track1.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(DataWord.of(horseValue1), track1.getStorageValue(horse, DataWord.of(horseKey1))); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addStorageRow(cow, DataWord.of(cowKey2), DataWord.of(cowValue2)); +// track2.addStorageRow(horse, DataWord.of(horseKey2), DataWord.of(horseValue2)); +// +// assertEquals(DataWord.of(cowValue1), track2.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(DataWord.of(horseValue1), track2.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track2.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track2.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track2.commit(); +// // leaving level_2 +// +// assertEquals(DataWord.of(cowValue1), track1.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(DataWord.of(horseValue1), track1.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track1.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track1.commit(); +// // leaving level_1 +// +// assertEquals(DataWord.of(cowValue1), repository.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(DataWord.of(horseValue1), repository.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), repository.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), repository.getStorageValue(horse, DataWord.of(horseKey2))); +// +// repository.close(); +// } +// +// @Test +// public void test16_2() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// byte[] horseKey1 = "key-h-1".getBytes(); +// byte[] horseValue1 = "val-h-1".getBytes(); +// +// byte[] cowKey2 = "key-c-2".getBytes(); +// byte[] cowValue2 = "val-c-2".getBytes(); +// +// byte[] horseKey2 = "key-h-2".getBytes(); +// byte[] horseValue2 = "val-h-2".getBytes(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addStorageRow(cow, DataWord.of(cowKey2), DataWord.of(cowValue2)); +// track2.addStorageRow(horse, DataWord.of(horseKey2), DataWord.of(horseValue2)); +// +// assertNull(track2.getStorageValue(cow, DataWord.of(cowKey1))); +// assertNull(track2.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track2.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track2.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track2.commit(); +// // leaving level_2 +// +// assertNull(track1.getStorageValue(cow, DataWord.of(cowKey1))); +// assertNull(track1.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track1.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track1.commit(); +// // leaving level_1 +// +// assertEquals(null, repository.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(null, repository.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), repository.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), repository.getStorageValue(horse, DataWord.of(horseKey2))); +// +// repository.close(); +// } +// +// @Test +// public void test16_3() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// byte[] horseKey1 = "key-h-1".getBytes(); +// byte[] horseValue1 = "val-h-1".getBytes(); +// +// byte[] cowKey2 = "key-c-2".getBytes(); +// byte[] cowValue2 = "val-c-2".getBytes(); +// +// byte[] horseKey2 = "key-h-2".getBytes(); +// byte[] horseValue2 = "val-h-2".getBytes(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addStorageRow(cow, DataWord.of(cowKey2), DataWord.of(cowValue2)); +// track2.addStorageRow(horse, DataWord.of(horseKey2), DataWord.of(horseValue2)); +// +// assertNull(track2.getStorageValue(cow, DataWord.of(cowKey1))); +// assertNull(track2.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track2.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track2.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track2.commit(); +// // leaving level_2 +// +// assertNull(track1.getStorageValue(cow, DataWord.of(cowKey1))); +// assertNull(track1.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// assertEquals(DataWord.of(horseValue2), track1.getStorageValue(horse, DataWord.of(horseKey2))); +// +// track1.rollback(); +// // leaving level_1 +// +// assertNull(repository.getStorageValue(cow, DataWord.of(cowKey1))); +// assertNull(repository.getStorageValue(horse, DataWord.of(horseKey1))); +// +// assertNull(repository.getStorageValue(cow, DataWord.of(cowKey2))); +// assertNull(repository.getStorageValue(horse, DataWord.of(horseKey2))); +// +// repository.close(); +// } +// +// @Test +// public void test16_4() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// byte[] horseKey1 = "key-h-1".getBytes(); +// byte[] horseValue1 = "val-h-1".getBytes(); +// +// byte[] cowKey2 = "key-c-2".getBytes(); +// byte[] cowValue2 = "val-c-2".getBytes(); +// +// byte[] horseKey2 = "key-h-2".getBytes(); +// byte[] horseValue2 = "val-h-2".getBytes(); +// +// Repository track = repository.startTracking(); +// track.addStorageRow(cow, DataWord.of(cowKey1), DataWord.of(cowValue1)); +// track.commit(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addStorageRow(cow, DataWord.of(cowKey2), DataWord.of(cowValue2)); +// +// track2.commit(); +// // leaving level_2 +// +// track1.commit(); +// // leaving level_1 +// +// assertEquals(DataWord.of(cowValue1), track1.getStorageValue(cow, DataWord.of(cowKey1))); +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// +// +// repository.close(); +// } +// +// +// @Test +// public void test16_5() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// byte[] horseKey1 = "key-h-1".getBytes(); +// byte[] horseValue1 = "val-h-1".getBytes(); +// +// byte[] cowKey2 = "key-c-2".getBytes(); +// byte[] cowValue2 = "val-c-2".getBytes(); +// +// byte[] horseKey2 = "key-h-2".getBytes(); +// byte[] horseValue2 = "val-h-2".getBytes(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// track1.addStorageRow(cow, DataWord.of(cowKey2), DataWord.of(cowValue2)); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// assertNull(track1.getStorageValue(cow, DataWord.of(cowKey1))); +// +// track2.commit(); +// // leaving level_2 +// +// track1.commit(); +// // leaving level_1 +// +// assertEquals(DataWord.of(cowValue2), track1.getStorageValue(cow, DataWord.of(cowKey2))); +// assertNull(track1.getStorageValue(cow, DataWord.of(cowKey1))); +// +// repository.close(); +// } +// +// +// +// +// @Test +// public void test17() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// +// byte[] cowKey1 = "key-c-1".getBytes(); +// byte[] cowValue1 = "val-c-1".getBytes(); +// +// // changes level_1 +// Repository track1 = repository.startTracking(); +// +// // changes level_2 +// Repository track2 = track1.startTracking(); +// track2.addStorageRow(cow, DataWord.of(cowKey1), DataWord.of(cowValue1)); +// assertEquals(DataWord.of(cowValue1), track2.getStorageValue(cow, DataWord.of(cowKey1))); +// track2.rollback(); +// // leaving level_2 +// +// track1.commit(); +// // leaving level_1 +// +// Assert.assertEquals(Hex.toHexString(HashUtil.EMPTY_TRIE_HASH), Hex.toHexString(repository.getRoot())); +// repository.close(); +// } +// +// @Test +// public void test18() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository repoTrack2 = repository.startTracking(); //track +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// byte[] pig = Hex.decode("F0B8C9D84DD2B877E0B952130B73E218106FEC04"); +// byte[] precompiled = Hex.decode("0000000000000000000000000000000000000002"); +// +// byte[] cowCode = Hex.decode("A1A2A3"); +// byte[] horseCode = Hex.decode("B1B2B3"); +// +// repository.saveCode(cow, cowCode); +// repository.saveCode(horse, horseCode); +// +// repository.delete(horse); +// +// assertEquals(true, repoTrack2.isExist(cow)); +// assertEquals(false, repoTrack2.isExist(horse)); +// assertEquals(false, repoTrack2.isExist(pig)); +// assertEquals(false, repoTrack2.isExist(precompiled)); +// } +// +// @Test +// public void test19() { +// +// RepositoryRoot repository = new RepositoryRoot(new HashMapDB()); +// Repository track = repository.startTracking(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// DataWord cowKey1 = DataWord.of("c1"); +// DataWord cowVal1 = DataWord.of("c0a1"); +// DataWord cowVal0 = DataWord.of("c0a0"); +// +// DataWord horseKey1 = DataWord.of("e1"); +// DataWord horseVal1 = DataWord.of("c0a1"); +// DataWord horseVal0 = DataWord.of("c0a0"); +// +// track.addStorageRow(cow, cowKey1, cowVal0); +// track.addStorageRow(horse, horseKey1, horseVal0); +// track.commit(); +// +// Repository track2 = repository.startTracking(); //track +// +// track2.addStorageRow(horse, horseKey1, horseVal0); +// Repository track3 = track2.startTracking(); +// +// ContractDetails cowDetails = track3.getContractDetails(cow); +// cowDetails.put(cowKey1, cowVal1); +// +// ContractDetails horseDetails = track3.getContractDetails(horse); +// horseDetails.put(horseKey1, horseVal1); +// +// track3.commit(); +// track2.rollback(); +// +// ContractDetails cowDetailsOrigin = repository.getContractDetails(cow); +// DataWord cowValOrin = cowDetailsOrigin.get(cowKey1); +// +// ContractDetails horseDetailsOrigin = repository.getContractDetails(horse); +// DataWord horseValOrin = horseDetailsOrigin.get(horseKey1); +// +// assertEquals(cowVal0, cowValOrin); +// assertEquals(horseVal0, horseValOrin); +// } +// +// +// @Test // testing for snapshot +// public void test20() { +// +//// MapDB stateDB = new MapDB(); +// Source stateDB = new NoDeleteSource<>(new HashMapDB()); +// RepositoryRoot repository = new RepositoryRoot(stateDB); +// byte[] root = repository.getRoot(); +// +// byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F"); +// +// DataWord cowKey1 = DataWord.of("c1"); +// DataWord cowKey2 = DataWord.of("c2"); +// DataWord cowVal1 = DataWord.of("c0a1"); +// DataWord cowVal0 = DataWord.of("c0a0"); +// +// DataWord horseKey1 = DataWord.of("e1"); +// DataWord horseKey2 = DataWord.of("e2"); +// DataWord horseVal1 = DataWord.of("c0a1"); +// DataWord horseVal0 = DataWord.of("c0a0"); +// +// Repository track2 = repository.startTracking(); //track +// track2.addStorageRow(cow, cowKey1, cowVal1); +// track2.addStorageRow(horse, horseKey1, horseVal1); +// track2.commit(); +// repository.commit(); +// +// byte[] root2 = repository.getRoot(); +// +// track2 = repository.startTracking(); //track +// track2.addStorageRow(cow, cowKey2, cowVal0); +// track2.addStorageRow(horse, horseKey2, horseVal0); +// track2.commit(); +// repository.commit(); +// +// byte[] root3 = repository.getRoot(); +// +// Repository snapshot = new RepositoryRoot(stateDB, root); +// ContractDetails cowDetails = snapshot.getContractDetails(cow); +// ContractDetails horseDetails = snapshot.getContractDetails(horse); +// assertEquals(null, cowDetails.get(cowKey1) ); +// assertEquals(null, cowDetails.get(cowKey2) ); +// assertEquals(null, horseDetails.get(horseKey1) ); +// assertEquals(null, horseDetails.get(horseKey2) ); +// +// +// snapshot = new RepositoryRoot(stateDB, root2); +// cowDetails = snapshot.getContractDetails(cow); +// horseDetails = snapshot.getContractDetails(horse); +// assertEquals(cowVal1, cowDetails.get(cowKey1)); +// assertEquals(null, cowDetails.get(cowKey2)); +// assertEquals(horseVal1, horseDetails.get(horseKey1) ); +// assertEquals(null, horseDetails.get(horseKey2) ); +// +// snapshot = new RepositoryRoot(stateDB, root3); +// cowDetails = snapshot.getContractDetails(cow); +// horseDetails = snapshot.getContractDetails(horse); +// assertEquals(cowVal1, cowDetails.get(cowKey1)); +// assertEquals(cowVal0, cowDetails.get(cowKey2)); +// assertEquals(horseVal1, horseDetails.get(horseKey1) ); +// assertEquals(horseVal0, horseDetails.get(horseKey2) ); +// } +// +// private boolean running = true; +// +// @Test // testing for snapshot +// public void testMultiThread() throws InterruptedException { +// // Add logging line to {@link org.ethereum.datasource.WriteCache} in the beginning of flushImpl() method: +// // System.out.printf("Flush start: %s%n", this); +// // to increase chance of failing. Also increasing waiting time may be helpful. +// final RepositoryImpl repository = new RepositoryRoot(new HashMapDB()); +// +// final byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826"); +// +// final DataWord cowKey1 = DataWord.of("c1"); +// final DataWord cowKey2 = DataWord.of("c2"); +// final DataWord cowVal0 = DataWord.of("c0a0"); +// +// Repository track2 = repository.startTracking(); //track +// track2.addStorageRow(cow, cowKey2, cowVal0); +// track2.commit(); +// repository.flush(); +// +// ContractDetails cowDetails = repository.getContractDetails(cow); +// assertEquals(cowVal0, cowDetails.get(cowKey2)); +// +// final CountDownLatch failSema = new CountDownLatch(1); +// +// for (int i = 0; i < 10; ++i) { +// new Thread(() -> { +// try { +// int cnt = 1; +// while (running) { +// Repository snap = repository.getSnapshotTo(repository.getRoot()).startTracking(); +// snap.addBalance(cow, BigInteger.TEN); +// snap.addStorageRow(cow, cowKey1, DataWord.of(cnt)); +// snap.rollback(); +// cnt++; +// } +// } catch (Throwable e) { +// e.printStackTrace(); +// failSema.countDown(); +// } +// }).start(); +// } +// +// new Thread(() -> { +// int cnt = 1; +// try { +// while(running) { +// Repository track21 = repository.startTracking(); //track +// DataWord cVal = DataWord.of(cnt); +// track21.addStorageRow(cow, cowKey1, cVal); +// track21.addBalance(cow, BigInteger.ONE); +// track21.commit(); +// +// repository.flush(); +// +// assertEquals(BigInteger.valueOf(cnt), repository.getBalance(cow)); +// assertEquals(cVal, repository.getStorageValue(cow, cowKey1)); +// assertEquals(cowVal0, repository.getStorageValue(cow, cowKey2)); +// cnt++; +// } +// } catch (Throwable e) { +// e.printStackTrace(); +// try { +// repository.addStorageRow(cow, cowKey1, DataWord.of(123)); +// } catch (Exception e1) { +// e1.printStackTrace(); +// } +// failSema.countDown(); +// } +// }).start(); +// +// failSema.await(10, TimeUnit.SECONDS); +// running = false; +// +// if (failSema.getCount() == 0) { +// throw new RuntimeException("Test failed."); +// } +// } +} diff --git a/core/src/test/java/org/platon/core/facade/CmdInterfaceTest.java b/core/src/test/java/org/platon/core/facade/CmdInterfaceTest.java new file mode 100644 index 0000000..71b1dd4 --- /dev/null +++ b/core/src/test/java/org/platon/core/facade/CmdInterfaceTest.java @@ -0,0 +1,30 @@ +package org.platon.core.facade; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.platon.core.config.CoreConfig; + +@Ignore +public class CmdInterfaceTest { + + @Test + public void testCliParseForHelp(){ + + String[] cmds = new String[]{"--help"}; + CmdInterface.call(cmds); + } + + @Test + public void testCliParse(){ + + System.setProperty(CoreConfig.PROPERTY_DB_DIR, "db-00001"); + System.setProperty(CoreConfig.PROPERTY_DB_RESET, "yes"); + + String[] cmds = new String[]{"-db","db-00002","-reset","no"}; + CmdInterface.call(cmds); + + Assert.assertEquals("db-00002", CoreConfig.getInstance().getConfig().getString(CoreConfig.PROPERTY_DB_DIR)); + Assert.assertEquals(false, CoreConfig.getInstance().getConfig().getBoolean(CoreConfig.PROPERTY_DB_RESET)); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/facade/PlatonFactoryTest.java b/core/src/test/java/org/platon/core/facade/PlatonFactoryTest.java new file mode 100644 index 0000000..12b9e14 --- /dev/null +++ b/core/src/test/java/org/platon/core/facade/PlatonFactoryTest.java @@ -0,0 +1,29 @@ +package org.platon.core.facade; + +import org.junit.Assert; +import org.junit.Test; +import org.platon.core.config.CommonConfig; +import org.platon.core.config.DefaultConfig; + +public class PlatonFactoryTest { + + @Test + public void testCreateByNoConfig(){ + Platon platon = PlatonFactory.createPlaton(); + Assert.assertNotNull(platon); + } + + @Test + public void testCreateByDefaultConfig(){ + Platon platon = PlatonFactory.createPlaton(DefaultConfig.class); + Assert.assertNotNull(platon); + } + + @Test + public void testCreateByCommonConfig(){ + Platon platon = PlatonFactory.createPlaton(CommonConfig.class); + Assert.assertNotNull(platon); + } + + +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/genesis/GenesisLoaderTest.java b/core/src/test/java/org/platon/core/genesis/GenesisLoaderTest.java new file mode 100644 index 0000000..f84b1ab --- /dev/null +++ b/core/src/test/java/org/platon/core/genesis/GenesisLoaderTest.java @@ -0,0 +1,16 @@ +package org.platon.core.genesis; + +import org.junit.Assert; +import org.junit.Test; + +public class GenesisLoaderTest { + + private final String genesisName = "genesis.json"; + + @Test + public void loadGenesisJson() { + GenesisJson genesisJson = GenesisLoader.loadGenesisJson(genesisName); + Assert.assertNotNull(genesisJson.getAccounts()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/keystore/KeystoreTest.java b/core/src/test/java/org/platon/core/keystore/KeystoreTest.java new file mode 100644 index 0000000..2420fef --- /dev/null +++ b/core/src/test/java/org/platon/core/keystore/KeystoreTest.java @@ -0,0 +1,137 @@ +package org.platon.core.keystore; + +import org.junit.Test; +import org.platon.common.AppenderName; +import org.platon.common.utils.Numeric; +import org.platon.crypto.ECKey; +import org.platon.slice.message.response.StringArrayResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.bouncycastle.util.encoders.Hex; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.junit.Assert.assertNotNull; + +public class KeystoreTest { + + private final static Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_KEY_STORE); + + Keystore fileSystemKeystore = new FileSystemKeystore() { + + Path keystorePath = null; + { + try { + keystorePath = Files.createTempDirectory("keystore"); + logger.debug("keystore path : " + keystorePath.getFileName()); + } catch (IOException e) { + e.printStackTrace(); + } + keystoreFormat = new KeystoreFormat(); + } + + @Override + public Path getKeyStoreLocation() { + return keystorePath; + } + }; + + @Test + public void encodeDecode() throws Exception { + final String password = "123"; + + // generate new random private key + final ECKey key = new ECKey(); + final String address = Numeric.toHexString(key.getAddress()); + + fileSystemKeystore.removeKey(address); + fileSystemKeystore.storeKey(key, password); + + ECKey loadedKey = fileSystemKeystore.loadStoredKey(address, password); + + fileSystemKeystore.removeKey(address); + } + + @Test + public void readCorrectKey() throws Exception { + final String password = "123"; + final String address = "dc212a894a3575c61eadfb012c8db93923d806f5"; + + fileSystemKeystore.removeKey(address); + fileSystemKeystore.storeRawKeystore(CORRECT_KEY, address); + + final ECKey key = fileSystemKeystore.loadStoredKey(address, password); + + fileSystemKeystore.removeKey(address); + + assertNotNull(key); + } + + @Test(expected = RuntimeException.class) + public void readCorrectKeyWrongPassword() throws Exception { + final String password = "1234"; + final String address = "dc212a894a3575c61eadfb012c8db93923d806f5"; + + fileSystemKeystore.removeKey(address); + fileSystemKeystore.storeRawKeystore(CORRECT_KEY, address); + + fileSystemKeystore.loadStoredKey(address, password); + } + + @Test(expected = RuntimeException.class) + public void importDuplicateKey() throws Exception { + // generate new random private key + final ECKey key = new ECKey(); + final String address = Hex.toHexString(key.getAddress()); + + try { + fileSystemKeystore.storeKey(key, address); + fileSystemKeystore.storeKey(key, address); + } finally { + fileSystemKeystore.removeKey(address); + } + } + + + @Test + public void testStream() { + String[] arr = new String[]{"a","b","c"}; + + /*StringArrayResponse result = Arrays.stream(arr) + .map(e -> { + return ByteString.copyFromUtf8(e); + }) + .collect(); +*/ + /*StringArrayResponse result = convertToResponse(arr); + + try { + result.getDataList().asByteStringList() + .stream(). + forEach(r -> { + try { + System.out.println(r.toString("UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + }); + }catch (Exception e) { + e.printStackTrace(); + }*/ + + + + //System.out.println(result.toByteString().toString()); + } + + public StringArrayResponse convertToResponse(String[] element){ + StringArrayResponse.Builder builder = StringArrayResponse.newBuilder(); + Arrays.stream(element).forEach(str -> builder.addData(str)); + return builder.build(); + } + + private static String CORRECT_KEY = "{\"address\":\"a3ae7747a0690701cc84b453524fa7c99afcd8ac\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"4baa65c9e3438e28c657a3585c5b444746578a5b0f35e1816e43146a09dc9f94\",\"cipherparams\":{\"iv\":\"bca4d9a043c68a9b9d995492d29653f5\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"eadb4203d8618141268903a9c8c0ace4f45954e5c4679257b89b874f24b56ea3\"},\"mac\":\"b1b34957940158569ed129f9bb4373979c78748bdf6e33354bcc922d2a207efa\"},\"id\":\"c985b75c-01ef-49b7-b7f0-0c2db4c299bc\",\"version\":3}"; +} diff --git a/core/src/test/java/org/platon/core/restucture/TransactionSendTest.java b/core/src/test/java/org/platon/core/restucture/TransactionSendTest.java new file mode 100644 index 0000000..e1eca0e --- /dev/null +++ b/core/src/test/java/org/platon/core/restucture/TransactionSendTest.java @@ -0,0 +1,235 @@ +package org.platon.core.restucture; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.typesafe.config.ConfigFactory; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; +import org.platon.common.utils.Numeric; +import org.platon.core.TransactionInfo; +import org.platon.core.block.proto.BlockHeaderProto; +import org.platon.core.config.CoreConfig; +import org.platon.core.facade.Platon; +import org.platon.core.facade.PlatonFactory; +import org.platon.core.facade.PlatonImpl; +import org.platon.core.keystore.FileSystemKeystore; +import org.platon.core.keystore.Keystore; +import org.platon.core.rpc.ProtoRpc; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.core.transaction.proto.ContractRequest; +import org.platon.core.transaction.proto.TransactionBody; +import org.platon.core.transaction.proto.TransactionType; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.platon.crypto.WalletUtil; +import org.platon.slice.message.request.TransactionBaseRequest; +import org.platon.slice.message.response.BlockResponse; +import org.platon.slice.message.response.StringResponse; +import org.platon.slice.message.response.TransactionReceiptResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Scanner; + +public class TransactionSendTest { + + @Test + public void testMain() throws Exception { + System.out.println("Start Platonj for test..."); + PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(TestConfig.class); + TestRunner test = platon.getApplicationContext().getBean(TestRunner.class); + test.runTest(); + } + + private static class TestConfig { + + private final String config = + "database.dir = db-01 \n" + + "database.reset = yes \n" + + "platon.keystore.dir = /c/users/jungle/Desktop/keystore \n"; + + @Autowired + ApplicationContext ctx; + + @Bean + public CoreConfig configProperties() { + CoreConfig configProperties = CoreConfig.getInstance(); + configProperties.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\""))); + return configProperties; + } + + @Bean + @Primary + public Platon platon() { + return new PlatonImpl(); + } + + @Bean + @Primary + public Keystore keystore() { + return new FileSystemKeystore(); + } + + @Bean + public TestRunner testRunner() { + return new TestRunner(); + } + + public ApplicationContext getCtx() { + return ctx; + } + } + + static class TestRunner { + + private final static BigInteger RETURN_BALANCE = BigInteger.valueOf(1111); + private final static long RETURN_BLOCK_NUM = 1; + private final static String RETURN_BLOCK_HASH = "ox1a1a1a1a"; + private final static String RETURN_TX_HASH = "0x2b2b2b2b2b2b2b2b"; + private final static BigInteger PRIVATE_KEY = new BigInteger("32650665894031292597140147254420778587605881617997786858854485181389018874929"); + private final static ECKey EC_KEY = ECKey.fromPrivate(PRIVATE_KEY); + private final static String ADDRESS = Numeric.toHexString(EC_KEY.getAddress()); + private final static String RECEIVER = "fG58UzwfKzAVXwYNaNUvkLSEPqYSkR240"; + private final static byte[] EMPTY_BYTES = new byte[]{}; + @Autowired + private ProtoRpc protoRpc; + @Autowired + private Keystore keystore; + @Autowired + private Platon platon; + + private Transaction createTx() { + Transaction tx = new Transaction( + TransactionType.TRANSACTION, + BigInteger.valueOf(100), + Numeric.hexStringToByteArray(ADDRESS), + RETURN_BLOCK_NUM, + Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), + BigInteger.valueOf(1000000), + BigInteger.valueOf(2000000), + new byte[]{}, + -1); + return tx; + } + + private TransactionInfo createTxInfo() { + TransactionReceipt txReceipt = new TransactionReceipt( + EMPTY_BYTES, + EMPTY_BYTES, + null, null); + txReceipt.setTransaction(createTx()); + + TransactionInfo txInfo = new TransactionInfo( + txReceipt, + Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), + 0); + + return txInfo; + } + + private BlockHeaderProto.BlockHeader createBlockHeader() { + BlockHeaderProto.BlockHeader.Builder headerBuilder = BlockHeaderProto.BlockHeader.newBuilder(); + headerBuilder.setTimestamp(System.currentTimeMillis() / 1000); + headerBuilder.setBloomLog(ByteString.EMPTY); + headerBuilder.setNumber(RETURN_BLOCK_NUM); + headerBuilder.setParentHash(ByteString.EMPTY); + return headerBuilder.build(); + } + + + private TransactionBaseRequest createTxRequest() throws Exception { + + // build tx + ContractRequest.Builder ctrReqBuild = ContractRequest.newBuilder(); + ctrReqBuild.setOperation("call"); + ctrReqBuild.setClassName("MyHello"); + ctrReqBuild.setData(ByteString.EMPTY); + + TransactionBody.Builder bodyBuild = TransactionBody.newBuilder(); + bodyBuild.setType(TransactionType.TRANSACTION); + bodyBuild.setValue(ByteString.copyFrom(BigInteger.TEN.toByteArray())); + bodyBuild.setReceiveAddress(ByteString.copyFrom(Numeric.hexStringToByteArray(ADDRESS))); + bodyBuild.setReferenceBlockNum(RETURN_BLOCK_NUM); + bodyBuild.setReferenceBlockHash(ByteString.copyFrom(Numeric.hexStringToByteArray(RETURN_BLOCK_HASH))); + bodyBuild.setEnergonPrice(ByteString.copyFrom(BigInteger.valueOf(11000).toByteArray())); + bodyBuild.setEnergonLimit(ByteString.copyFrom(BigInteger.valueOf(20000).toByteArray())); + bodyBuild.setData(Any.pack(ctrReqBuild.build())); + + TransactionBody body = bodyBuild.build(); + + TransactionBaseRequest.Builder baseBuild = TransactionBaseRequest.newBuilder(); + byte[] messageHash = HashUtil.sha3(bodyBuild.build().toByteArray()); + byte chainId = 1; + baseBuild.setSignature(new String(WalletUtil.sign(messageHash, chainId, EC_KEY))); + baseBuild.setFrom(ADDRESS); + baseBuild.setBody(body); + + return baseBuild.build(); + } + + public void runTest() throws Exception { + + System.out.println(EC_KEY.getPrivKey()); + // fG58UzwfKzAVXwYNaNUvkLSEPqYSkR25o + System.out.println(Numeric.toHexString(EC_KEY.getAddress())); + System.out.println(Arrays.toString(EC_KEY.getAddress())); + // - atpGetBalance + String addr = ADDRESS; + //byte[] addrAsBytes = Numeric.hexStringToByteArray(addr); + //BigInteger balance01 = platon.getBalance(addrAsBytes); + //StringResponse balance = protoRpc.atpGetBalance(addr, "latest"); + + //System.out.println("balance:" + balance.getData()); + //Assert.assertEquals(RETURN_BALANCE.intValue(), Numeric.toBigInt(balance.getData()).intValue()); + + // - atpBlockNumber + StringResponse blockNumber = protoRpc.atpBlockNumber(); + //Assert.assertEquals(RETURN_BLOCK_NUM, Numeric.toBigInt(blockNumber.getData())); + + // - atpGetBlockTransactionCountByNumber + StringResponse txCount = protoRpc.atpGetBlockTransactionCountByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM))); + //Assert.assertEquals(Numeric.toHexStringWithPrefix(BigInteger.valueOf(1)), txCount.getData()); + + // - atpSendTransaction + //protoRpc.personalUnlockAccount(ADDRESS, "11112", Numeric.toHexStringWithPrefix(BigInteger.valueOf(Integer.MAX_VALUE))); + //StringResponse txHash = protoRpc.atpSendTransaction(createTxRequest()); + //System.out.println("atpSendTransaction -> txHash : " + txHash.getData()); + + // - atpSendFillTransaction + StringResponse txHash = protoRpc.atpSendFillTransaction(createTxRequest()); + System.out.println("atpSendFillTransaction -> txHash : " + txHash.getData()); + + byte[] testHash = Hex.decode("d5934f3d1a82934e2056515626efa596c79628f1199a247e0d88c91e058dba46"); + + // - atpGetTransactionReceipt + TransactionReceiptResponse receipt = protoRpc.atpGetTransactionReceipt("d5934f3d1a82934e2056515626efa596c79628f1199a247e0d88c91e058dba46"); +// System.out.println(receipt); +// while(receipt == null){ +// Thread.sleep(1000); +// receipt = protoRpc.atpGetTransactionReceipt(txHash.getData()); +// System.out.println("////////////// 查询了没找到\\\\\\\\\\\\\\\\\\"); +// } + System.out.println("..."); + + // - atpGetBlockByNumber + BlockResponse blockResponse = protoRpc.atpGetBlockByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM)), true); + //Assert.assertNotNull(blockResponse); + + // - atpGetBlockByHash + //BlockResponse blockResponse = protoRpc.atpGetBlockByHash(RETURN_BLOCK_HASH, true); + //Assert.assertNotNull(blockResponse); + + // - atpGetTransactionByHash + //TransactionResponse txResponse = protoRpc.atpGetTransactionByHash(RETURN_TX_HASH); + //Assert.assertNotNull(txResponse); + new Scanner(System.in).next(); + } + + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/rpc/ProtoRpcTest.java b/core/src/test/java/org/platon/core/rpc/ProtoRpcTest.java new file mode 100644 index 0000000..6bfb4a5 --- /dev/null +++ b/core/src/test/java/org/platon/core/rpc/ProtoRpcTest.java @@ -0,0 +1,278 @@ +//package org.platon.core.rpc; +// +//import com.google.protobuf.Any; +//import com.google.protobuf.ByteString; +//import com.typesafe.config.ConfigFactory; +//import org.junit.Assert; +//import org.junit.Test; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.Mockito; +//import org.mockito.MockitoAnnotations; +//import org.platon.common.config.ConfigProperties; +//import org.platon.common.utils.Numeric; +//import org.platon.core.TransactionInfo; +//import org.platon.core.block.Block; +//import org.platon.core.block.proto.BlockHeaderProto; +//import org.platon.core.facade.Platon; +//import org.platon.core.facade.PlatonFactory; +//import org.platon.core.facade.PlatonImpl; +//import org.platon.core.keystore.FileSystemKeystore; +//import org.platon.core.keystore.Keystore; +//import org.platon.core.transaction.Transaction; +//import org.platon.core.transaction.TransactionReceipt; +//import org.platon.core.transaction.proto.ContractRequest; +//import org.platon.core.transaction.proto.TransactionBody; +//import org.platon.core.transaction.proto.TransactionType; +//import org.platon.crypto.ECKey; +//import org.platon.crypto.HashUtil; +//import org.platon.crypto.WalletUtil; +//import org.platon.slice.message.request.TransactionBaseRequest; +//import org.platon.slice.message.response.StringArrayResponse; +//import org.platon.slice.message.response.StringResponse; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.ApplicationContext; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Primary; +// +//import javax.annotation.PostConstruct; +//import java.math.BigInteger; +//import java.util.List; +//import java.util.Scanner; +//import java.util.concurrent.CompletableFuture; +// +//import static org.mockito.Matchers.any; +// +//public class ProtoRpcTest { +// +// // 自定义配置文件 +// private static class TestConfig { +// +// private final String config = +// "database.dir = db-01 \n" + +// "database.reset = yes \n" + +// "platon.keystore.dir = /c/users/jungle/Desktop/keystore \n"; +// +// @Autowired +// ApplicationContext ctx; +// +// @Bean +// public ConfigProperties configProperties(){ +// ConfigProperties configProperties = new ConfigProperties(); +// configProperties.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\""))); +// return configProperties; +// } +// +// @Bean @Primary +// public Platon platon(){ +// return new PlatonImpl(); +// } +// +// @Bean @Primary +// public Keystore keystore(){ +// return new FileSystemKeystore(); +// } +// +// @Bean +// public TestRunner testRunner(){ +// return new TestRunner(); +// } +// +// public ApplicationContext getCtx(){ +// return ctx; +// } +// } +// +// // protoRpcTest +// static class TestRunner { +// +// @Autowired @InjectMocks +// private ProtoRpc protoRpc; +// +// @Autowired +// private Keystore keystore; +// +// @Mock +// private Platon platon; +// +// private final static BigInteger RETURN_BALANCE = BigInteger.valueOf(1111); +// private final static long RETURN_BLOCK_NUM = 1; +// private final static String RETURN_BLOCK_HASH = "ox1a1a1a1a"; +// private final static String RETURN_TX_HASH = "0x2b2b2b2b2b2b2b2b"; +// private final static ECKey EC_KEY = new ECKey(); +// private final static String ADDRESS = Numeric.toHexString(EC_KEY.getAddress()); +// +// private final static byte[] EMPTY_BYTES = new byte[]{}; +// +// @PostConstruct +// public void init(){ +// MockitoAnnotations.initMocks(this); +// Mockito.when(platon.getBalance(any())).thenReturn(RETURN_BALANCE); +// +// Mockito.when(platon.getBestBlock()).thenReturn(createBlock()); +// Mockito.when(platon.getBlockByHash(any(byte[].class))).thenReturn(createBlock()); +// Mockito.when(platon.getBlockByNumber(any(Long.class))).thenReturn(createBlock()); +// Mockito.when(platon.getTransactionInfo(any(byte[].class))).thenReturn(createTxInfo()); +// Mockito.when(platon.submitTransaction(any())).thenReturn(CompletableFuture.completedFuture(createTx())); +// } +// +// private Transaction createTx() { +// Transaction tx = new Transaction( +// TransactionType.TRANSACTION, +// BigInteger.valueOf(100), +// Numeric.hexStringToByteArray(ADDRESS), +// RETURN_BLOCK_NUM, +// Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), +// BigInteger.valueOf(1000000), +// BigInteger.valueOf(2000000), +// new byte[]{}, +// -1); +// return tx; +// } +// +// private TransactionInfo createTxInfo() { +// TransactionReceipt txReceipt = new TransactionReceipt( +// EMPTY_BYTES, +// EMPTY_BYTES, +// null, null); +// txReceipt.setTransaction(createTx()); +// +// TransactionInfo txInfo = new TransactionInfo( +// txReceipt, +// Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), +// 0); +// +// return txInfo; +// } +// +// private BlockHeaderProto.BlockHeader createBlockHeader() { +// BlockHeaderProto.BlockHeader.Builder headerBuilder = BlockHeaderProto.BlockHeader.newBuilder(); +// headerBuilder.setTimestamp(System.currentTimeMillis()/1000); +// headerBuilder.setBloomLog(ByteString.EMPTY); +// headerBuilder.setNumber(RETURN_BLOCK_NUM); +// headerBuilder.setParentHash(ByteString.EMPTY); +// return headerBuilder.build(); +// } +// +// private Block createBlock() { +// Block block = new Block(); +// block.sealBlock(createBlockHeader().toByteArray()); +// return block; +// } +// +// private TransactionBaseRequest createTxRequest() throws Exception { +// +// // build tx +// ContractRequest.Builder ctrReqBuild = ContractRequest.newBuilder(); +// ctrReqBuild.setOperation("call"); +// ctrReqBuild.setClassName("MyHello"); +// ctrReqBuild.setData(ByteString.EMPTY); +// +// TransactionBody.Builder bodyBuild = TransactionBody.newBuilder(); +// bodyBuild.setType(TransactionType.CONTRACT_CALL); +// bodyBuild.setValue(ByteString.copyFrom(BigInteger.ZERO.toByteArray())); +// bodyBuild.setReceiveAddress(ByteString.copyFrom(Numeric.hexStringToByteArray(ADDRESS))); +// bodyBuild.setReferenceBlockNum(RETURN_BLOCK_NUM); +// bodyBuild.setReferenceBlockHash(ByteString.copyFrom(Numeric.hexStringToByteArray(RETURN_BLOCK_HASH))); +// bodyBuild.setEnergonPrice(ByteString.copyFrom(BigInteger.valueOf(10000).toByteArray())); +// bodyBuild.setEnergonLimit(ByteString.copyFrom(BigInteger.valueOf(20000).toByteArray())); +// bodyBuild.setData(Any.pack(ctrReqBuild.build())); +// +// TransactionBody body = bodyBuild.build(); +// +// TransactionBaseRequest.Builder baseBuild = TransactionBaseRequest.newBuilder(); +// byte[] messageHash = HashUtil.sha3(bodyBuild.build().toByteArray()); +// baseBuild.setSignature(new String(WalletUtil.sign(messageHash,EC_KEY))); +// baseBuild.setFrom(ADDRESS); +// +// baseBuild.setBody(body); +// +// return baseBuild.build(); +// } +// +// public void runTest() throws Exception { +// +// // - atpGetBalance +// String addr = "0xa3ae7747a0690701cc84b453524fa7c99afcd8ac"; +// byte[] addrAsBytes = Numeric.hexStringToByteArray(addr); +// //BigInteger balance01 = platon.getBalance(addrAsBytes); +// StringResponse balance = protoRpc.atpGetBalance(addr, "latest"); +// +// Assert.assertEquals(RETURN_BALANCE.intValue(), Numeric.toBigInt(balance.getData()).intValue()); +// +// // - atpBlockNumber +// //StringResponse blockNumber = protoRpc.atpBlockNumber(); +// //Assert.assertEquals(RETURN_BLOCK_NUM, Numeric.toBigInt(blockNumber.getData())); +// +// // - atpGetBlockTransactionCountByNumber +// //StringResponse txCount = protoRpc.atpGetBlockTransactionCountByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM))); +// //Assert.assertEquals(Numeric.toHexStringWithPrefix(BigInteger.valueOf(1)), txCount.getData()); +// +// // - atpSendTransaction +// //protoRpc.personalUnlockAccount(ADDRESS, "11112", Numeric.toHexStringWithPrefix(BigInteger.valueOf(Integer.MAX_VALUE))); +// //StringResponse txHash = protoRpc.atpSendTransaction(createTxRequest()); +// //System.out.println("atpSendTransaction -> txHash : " + txHash.getData()); +// +// // - atpSendFillTransaction +// StringResponse txHash = protoRpc.atpSendFillTransaction(createTxRequest()); +// System.out.println("atpSendFillTransaction -> txHash : " + txHash.getData()); +// +// // - atpGetBlockByHash +// //BlockResponse blockResponse = protoRpc.atpGetBlockByHash(RETURN_BLOCK_HASH, true); +// //Assert.assertNotNull(blockResponse); +// +// // - atpGetBlockByNumber +// //blockResponse = protoRpc.atpGetBlockByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM)), true); +// //Assert.assertNotNull(blockResponse); +// +// // - atpGetTransactionByHash +// //TransactionResponse txResponse = protoRpc.atpGetTransactionByHash(RETURN_TX_HASH); +// //Assert.assertNotNull(txResponse); +// new Scanner(System.in).next(); +// } +// +// public void testAtpAccounts() throws Exception { +// +// // 生成几个文件 +// for (int i = 0; i < 5; i++) { +// keystore.storeKey(new ECKey(), "1111" + i); +// } +// +// // store wallet file +// StringArrayResponse response = protoRpc.atpAccounts(); +// List byteStringList = response.getDataList().asByteStringList(); +// for (int i = 0; i < byteStringList.size(); i++) { +// System.out.println(Numeric.toHexString(byteStringList.get(i).toByteArray())); +// } +// } +// } +// +// @Test +// public void testTestConig(){ +// System.out.println("Start test config."); +// PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(TestConfig.class); +// Keystore keystore = platon.getApplicationContext().getBean(Keystore.class); +// Assert.assertNotNull(keystore); +// } +// +// @Test +// public void testMain() throws Exception { +// System.out.println("Start Platonj..."); +// PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(TestConfig.class); +// TestRunner test = platon.getApplicationContext().getBean(TestRunner.class); +// //test.testAtpAccounts(); +// test.runTest(); +// } +// +// @Test +// public void testBlockCode() { +// { +// System.out.println("\u001b[31m"); +// System.out.println("你大爷的大爷"); +// System.out.println("\u001b[0m"); +// } +// { +// System.out.println("..."); +// } +// } +//} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/rpc/TxTest.java b/core/src/test/java/org/platon/core/rpc/TxTest.java new file mode 100644 index 0000000..64db4b2 --- /dev/null +++ b/core/src/test/java/org/platon/core/rpc/TxTest.java @@ -0,0 +1,241 @@ +package org.platon.core.rpc; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.typesafe.config.ConfigFactory; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Assert; +import org.junit.Test; +import org.platon.common.utils.Numeric; +import org.platon.core.TransactionInfo; +import org.platon.core.block.Block; +import org.platon.core.block.proto.BlockHeaderProto; +import org.platon.core.config.CoreConfig; +import org.platon.core.facade.Platon; +import org.platon.core.facade.PlatonFactory; +import org.platon.core.facade.PlatonImpl; +import org.platon.core.keystore.FileSystemKeystore; +import org.platon.core.keystore.Keystore; +import org.platon.core.transaction.Transaction; +import org.platon.core.transaction.TransactionReceipt; +import org.platon.core.transaction.proto.ContractRequest; +import org.platon.core.transaction.proto.TransactionBody; +import org.platon.core.transaction.proto.TransactionType; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.platon.crypto.WalletUtil; +import org.platon.slice.message.request.TransactionBaseRequest; +import org.platon.slice.message.response.StringResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Scanner; + +public class TxTest { + + @Test + public void testMain() throws Exception { + System.out.println("Start Platonj for test..."); + PlatonImpl platon = (PlatonImpl) PlatonFactory.createPlaton(TestConfig.class); + TestRunner test = platon.getApplicationContext().getBean(TestRunner.class); + test.runTest(); + } + + private static class TestConfig { + + private final String config = + "database.dir = db-01 \n" + + "database.reset = yes \n" + + "platon.keystore.dir = /c/users/jungle/Desktop/keystore \n"; + + @Autowired + ApplicationContext ctx; + + @Bean + public CoreConfig configProperties() { + CoreConfig configProperties = CoreConfig.getInstance(); + configProperties.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\""))); + return configProperties; + } + + @Bean + @Primary + public Platon platon() { + return new PlatonImpl(); + } + + @Bean + @Primary + public Keystore keystore() { + return new FileSystemKeystore(); + } + + @Bean + public TestRunner testRunner() { + return new TestRunner(); + } + + public ApplicationContext getCtx() { + return ctx; + } + } + + static class TestRunner { + + private final static BigInteger RETURN_BALANCE = BigInteger.valueOf(1111); + private final static long RETURN_BLOCK_NUM = 1; + private final static String RETURN_BLOCK_HASH = "ox1a1a1a1a"; + private final static String RETURN_TX_HASH = "0x2b2b2b2b2b2b2b2b"; + private final static BigInteger PRIVATE_KEY = new BigInteger("32650665894031292597140147254420778587605881617997786858854485181389018874929"); + private final static ECKey EC_KEY = ECKey.fromPrivate(PRIVATE_KEY); + private final static String ADDRESS = Numeric.toHexString(EC_KEY.getAddress()); + private final static String RECEIVER = "fG58UzwfKzAVXwYNaNUvkLSEPqYSkR240"; + private final static byte[] EMPTY_BYTES = new byte[]{}; + @Autowired + private ProtoRpc protoRpc; + @Autowired + private Keystore keystore; + @Autowired + private Platon platon; + + private Transaction createTx() { + Transaction tx = new Transaction( + TransactionType.TRANSACTION, + BigInteger.valueOf(100), + Numeric.hexStringToByteArray(ADDRESS), + RETURN_BLOCK_NUM, + Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), + BigInteger.valueOf(1000000), + BigInteger.valueOf(2000000), + new byte[]{}, + -1); + return tx; + } + + private TransactionInfo createTxInfo() { + TransactionReceipt txReceipt = new TransactionReceipt( + EMPTY_BYTES, + EMPTY_BYTES, + null, null); + txReceipt.setTransaction(createTx()); + + TransactionInfo txInfo = new TransactionInfo( + txReceipt, + Numeric.hexStringToByteArray(RETURN_BLOCK_HASH), + 0); + + return txInfo; + } + + private BlockHeaderProto.BlockHeader createBlockHeader() { + BlockHeaderProto.BlockHeader.Builder headerBuilder = BlockHeaderProto.BlockHeader.newBuilder(); + headerBuilder.setTimestamp(System.currentTimeMillis() / 1000); + headerBuilder.setBloomLog(ByteString.EMPTY); + headerBuilder.setNumber(RETURN_BLOCK_NUM); + headerBuilder.setParentHash(ByteString.EMPTY); + return headerBuilder.build(); + } + + private Block createBlock() { + //Block block = new Block(); + //block.sealBlock(createBlockHeader().toByteArray()); + //return block; + //todo: 此处缺失 + return null; + } + + private TransactionBaseRequest createTxRequest() throws Exception { + + // build tx + ContractRequest.Builder ctrReqBuild = ContractRequest.newBuilder(); + ctrReqBuild.setOperation("call"); + ctrReqBuild.setClassName("MyHello"); + ctrReqBuild.setData(ByteString.EMPTY); + + TransactionBody.Builder bodyBuild = TransactionBody.newBuilder(); + bodyBuild.setType(TransactionType.TRANSACTION); + bodyBuild.setValue(ByteString.copyFrom(BigInteger.TEN.toByteArray())); + bodyBuild.setReceiveAddress(ByteString.copyFrom(Numeric.hexStringToByteArray(ADDRESS))); + bodyBuild.setReferenceBlockNum(RETURN_BLOCK_NUM); + bodyBuild.setReferenceBlockHash(ByteString.copyFrom(Numeric.hexStringToByteArray(RETURN_BLOCK_HASH))); + bodyBuild.setEnergonPrice(ByteString.copyFrom(BigInteger.valueOf(11000).toByteArray())); + bodyBuild.setEnergonLimit(ByteString.copyFrom(BigInteger.valueOf(20000).toByteArray())); + bodyBuild.setData(Any.pack(ctrReqBuild.build())); + + TransactionBody body = bodyBuild.build(); + + TransactionBaseRequest.Builder baseBuild = TransactionBaseRequest.newBuilder(); + byte[] messageHash = HashUtil.sha3(bodyBuild.build().toByteArray()); + byte chainId = 1; + baseBuild.setSignature(new String(WalletUtil.sign(messageHash, chainId, EC_KEY))); + baseBuild.setFrom(ADDRESS); + baseBuild.setBody(body); + + return baseBuild.build(); + } + + public void runTest() throws Exception { + + System.out.println(EC_KEY.getPrivKey()); + // fG58UzwfKzAVXwYNaNUvkLSEPqYSkR25o + System.out.println(Numeric.toHexString(EC_KEY.getAddress())); + System.out.println(Arrays.toString(EC_KEY.getAddress())); + // - atpGetBalance + String addr = ADDRESS; + //byte[] addrAsBytes = Numeric.hexStringToByteArray(addr); + //BigInteger balance01 = platon.getBalance(addrAsBytes); + //StringResponse balance = protoRpc.atpGetBalance(addr, "latest"); + + //System.out.println("balance:" + balance.getData()); + //Assert.assertEquals(RETURN_BALANCE.intValue(), Numeric.toBigInt(balance.getData()).intValue()); + + // - atpBlockNumber + StringResponse blockNumber = protoRpc.atpBlockNumber(); + Assert.assertEquals(RETURN_BLOCK_NUM, Numeric.toBigInt(blockNumber.getData())); + + // - atpGetBlockTransactionCountByNumber + //StringResponse txCount = protoRpc.atpGetBlockTransactionCountByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM))); + //Assert.assertEquals(Numeric.toHexStringWithPrefix(BigInteger.valueOf(1)), txCount.getData()); + + // - atpSendTransaction + //protoRpc.personalUnlockAccount(ADDRESS, "11112", Numeric.toHexStringWithPrefix(BigInteger.valueOf(Integer.MAX_VALUE))); + //StringResponse txHash = protoRpc.atpSendTransaction(createTxRequest()); + //System.out.println("atpSendTransaction -> txHash : " + txHash.getData()); + + // - atpSendFillTransaction + //StringResponse txHash = protoRpc.atpSendFillTransaction(createTxRequest()); + //System.out.println("atpSendFillTransaction -> txHash : " + txHash.getData()); + + byte[] testHash = Hex.decode("d5934f3d1a82934e2056515626efa596c79628f1199a247e0d88c91e058dba46"); + + // - atpGetTransactionReceipt + //TransactionReceiptResponse receipt = protoRpc.atpGetTransactionReceipt("d5934f3d1a82934e2056515626efa596c79628f1199a247e0d88c91e058dba46"); +// System.out.println(receipt); +// while(receipt == null){ +// Thread.sleep(1000); +// receipt = protoRpc.atpGetTransactionReceipt(txHash.getData()); +// System.out.println("////////////// 查询了没找到\\\\\\\\\\\\\\\\\\"); +// } + System.out.println("..."); + + // - atpGetBlockByNumber + //BlockResponse blockResponse = protoRpc.atpGetBlockByNumber(Numeric.toHexStringWithPrefix(BigInteger.valueOf(RETURN_BLOCK_NUM)), true); + //Assert.assertNotNull(blockResponse); + + // - atpGetBlockByHash + //BlockResponse blockResponse = protoRpc.atpGetBlockByHash(RETURN_BLOCK_HASH, true); + //Assert.assertNotNull(blockResponse); + + // - atpGetTransactionByHash + //TransactionResponse txResponse = protoRpc.atpGetTransactionByHash(RETURN_TX_HASH); + //Assert.assertNotNull(txResponse); + new Scanner(System.in).next(); + } + + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/state/StateTest.java b/core/src/test/java/org/platon/core/state/StateTest.java new file mode 100644 index 0000000..5d1f2d0 --- /dev/null +++ b/core/src/test/java/org/platon/core/state/StateTest.java @@ -0,0 +1,105 @@ +//package org.platon.core.state; +// +//import org.bouncycastle.util.encoders.Hex; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.platon.common.utils.Numeric; +//import org.platon.core.Account; +//import org.platon.core.block.Block; +//import org.platon.core.block.BlockHeader; +//import org.platon.core.chain.Chain; +//import org.platon.core.config.CommonConfig; +//import org.platon.core.config.DefaultConfig; +//import org.platon.core.transaction.Transaction; +//import org.platon.core.transaction.TransactionPoolTest; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.test.context.ContextConfiguration; +//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +// +//import java.math.BigInteger; +// +//@RunWith(SpringJUnit4ClassRunner.class) +//@ContextConfiguration(classes = DefaultConfig.class) +//public class StateTest { +// +// @Autowired +// private Chain chain; +// +// @Autowired +// private CommonConfig commonConfig; +// +// private State state; +// +// private Block genesisBlock; +// +// private byte[] address = Numeric.hexStringToByteArray("0xa3ae7747a0690701cc84b453524fa7c99afcd8ac"); +// +// @Before +// public void setUp() { +// genesisBlock = chain.genesisBlock(); +// state = chain.genesisBlock().getState(); +// } +// +// @Test +// public void balance() { +// BigInteger balance = state.balance(address); +// Assert.assertNotNull(balance); +// } +// +// @Test +// public void setStorage() { +// byte[] key = Hex.decode("0a791201011a2166473538557a77664b7a41565877594e614e55766b4c534550"); +// byte[] value = "0a791201011a2166473538557a77664b7a41565877594e614e55766b4c534550".getBytes(); +// state.setStorage(address, key, value); +// } +// +// @Test +// public void executeTransaction() { +// Transaction tx = TransactionPoolTest.getTx1(); +// BlockHeader header = new BlockHeader(); +// header.propagatedBy(commonConfig.systemConfig().getGenesisBlock().info()); +// state.executeTransaction(header, chain.lastHashes(), new byte[0], tx); +// } +// +// @Test +// public void transferBalance() { +// byte[] receiver = Numeric.hexStringToByteArray("0xa3ae7747a0690701cc84b453524fa7c99afcd8ac"); +// BigInteger value = BigInteger.TEN; +// +// state.transferBalance(address, receiver, value); +// +// Assert.assertTrue(-1 != state.balance(receiver).compareTo(BigInteger.TEN)); +// } +// +// @Test +// public void commit() { +// transferBalance(); +// state.commit();; +// } +// +// @Test +// public void rollback() { +// transferBalance(); +// state.rollback(0);; +// } +// +// @Test +// public void setRoot() { +// state.setRoot(commonConfig.systemConfig().getGenesisBlock().getState().getRoot()); +// } +// +// @Test +// public void getRoot() { +// byte[] root = state.getRoot(); +// Assert.assertNotNull(root); +// } +// +// @Test +// public void createAccount() { +// byte[] addr = Numeric.hexStringToByteArray("0xa3ae7747a0690701cc84b453524fa7c99afcd8ac"); +// Account account = state.createAccount(addr); +// Assert.assertNotNull(account); +// } +//} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/transaction/TransactionPoolTest.java b/core/src/test/java/org/platon/core/transaction/TransactionPoolTest.java new file mode 100644 index 0000000..4d50b41 --- /dev/null +++ b/core/src/test/java/org/platon/core/transaction/TransactionPoolTest.java @@ -0,0 +1,52 @@ +package org.platon.core.transaction; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; + +public class TransactionPoolTest { + + private static byte[] tx1Bytes = Hex.decode("0a791201011a2166473538557a77664b7a41565877594e614e55766b4c5345507159536b5232356f30013a05ef1a1a1a1a42022af84a024e2052400a2d747970652e676f6f676c65617069732e636f6d2f706c61746f6e2e74782e436f6e747261637452657175657374120f0a0463616c6c12074d7948656c6c6f12584a646a64594d6e3641585253507434736d30573161303830766b44524658506d2f6d586c5a75534b585334724e5045384e644b4e6f4c715076346d71755657784c714748714c5961486754476a5977576b504c474643633d"); + private static byte[] tx2Bytes = Hex.decode("0a7912010a1a2166473538557a77664b7a41565877594e614e55766b4c5345507159536b5232356f30013a05ef1a1a1a1a42022af84a024e2052400a2d747970652e676f6f676c65617069732e636f6d2f706c61746f6e2e74782e436f6e747261637452657175657374120f0a0463616c6c12074d7948656c6c6f12584a595331346d2f65617431375669676e62625841485a4a614a4f6d36736134614f464d677770773143526d73572b546b4a44645363774c736e437575617a6e7a796e2f6d65316f5465304c74436b6166684c785a4842593d"); + + TransactionPool pool = TransactionPool.getInstance(); + private static Transaction tx1 = new Transaction(tx1Bytes); + private static Transaction tx2 = new Transaction(tx2Bytes); + + @Test + public void inject() { + pool.clear(); + pool.inject(tx1); + pool.inject(tx2); + Assert.assertTrue(pool.isKnown(tx1.getHash())); + Assert.assertTrue(pool.isKnown(tx2.getHash())); + } + + @Test + public void isKnown() { + inject(); + Assert.assertTrue(TransactionPool.getInstance().isKnown(tx1.getHash())); + pool.drop(tx2); + Assert.assertTrue(!TransactionPool.getInstance().isKnown(tx2.getHash())); + } + + @Test + public void consume() { + inject(); + HashSet avoid = new HashSet<>(); + avoid.add(tx2.getHash()); + + ArrayList ret = pool.consume(10, avoid); + Assert.assertEquals(ret.size(), 1); + } + + public static Transaction getTx1() { + return tx1; + } + public static Transaction getTx2() { + return tx2; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/platon/core/transaction/TransactionReceiptTest.java b/core/src/test/java/org/platon/core/transaction/TransactionReceiptTest.java new file mode 100644 index 0000000..d5672fe --- /dev/null +++ b/core/src/test/java/org/platon/core/transaction/TransactionReceiptTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.transaction; + + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Assert; +import org.junit.Test; +import org.platon.common.wrapper.DataWord; +import org.platon.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Roman Mandeleil + * @since 05.12.2014 + */ +public class TransactionReceiptTest { + + private static final Logger logger = LoggerFactory.getLogger("TransactionReceiptTest"); + + @Test // rlp decode + public void test_flow() { + byte[] key = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + byte stateRoot[] = HashUtil.sha3(key); + byte[] cumulativeEnergon = Hex.decode("50005050"); + Bloom bloomFilter = new Bloom(); + + byte[] address = Hex.decode("f82804881bc16d674ec8000094cd2a3d9f938e13"); + List topics = new ArrayList<>(); + String data1 = "topics1"; + String data2 = "topics2"; + String data3 = "topics3"; + topics.add(DataWord.of(data1.getBytes())); + topics.add(DataWord.of(data2.getBytes())); + topics.add(DataWord.of(data3.getBytes())); + byte[] data = Hex.decode("8aa0966265cc49fa1f10f0445f035258d116563931022a3570"); + LogInfo log1 = new LogInfo(address, topics, data); + + byte[] addressAnother = Hex.decode("f82804881bc16d674ec8000094cd2a3d9f938e13"); + List topicsAnother = new ArrayList<>(); + String another1 = "Another1"; + String another2 = "Another2"; + String another3 = "Another3"; + topics.add(DataWord.of(another1.getBytes())); + topics.add(DataWord.of(another2.getBytes())); + topics.add(DataWord.of(another3.getBytes())); + byte[] dataAnother = Hex.decode("8aa0966265cc49fa1f10f0445f035258d116563931022a3570"); + LogInfo log2 = new LogInfo(addressAnother, topicsAnother, dataAnother); + + List logInfoList = new ArrayList<>(); + logInfoList.add(log1); + logInfoList.add(log2); + byte[] energonUsed = Hex.decode("5050"); + byte[] executionResult = Hex.decode("01"); + + TransactionReceipt txRep = new TransactionReceipt(stateRoot, cumulativeEnergon, bloomFilter, logInfoList); + txRep.setEnergonUsed(energonUsed); + txRep.setExecutionResult(executionResult); + + byte txRepEncode[] = txRep.getEncoded(); + + TransactionReceipt txRepNew = new TransactionReceipt(txRepEncode); + + Assert.assertArrayEquals(stateRoot, txRepNew.getStateRoot()); + Assert.assertArrayEquals(cumulativeEnergon,txRepNew.getCumulativeEnergon()); + Assert.assertTrue(logInfoList.toString().compareTo(txRepNew.getLogInfoList().toString()) == 0); + } + + @Test + public void test_2() { +// byte[] rlp = Hex.decode("f9012ea02d0cd041158c807326dae7cf5f044f3b9d4bd91a378cc55781b75455206e0c368339dc68b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c08252088080"); +// +// TransactionReceipt txReceipt = new TransactionReceipt(rlp); +// txReceipt.setExecutionResult(new byte[0]); +// byte[] encoded = txReceipt.getEncoded(); +// TransactionReceipt txReceipt1 = new TransactionReceipt(encoded); +// System.out.println(txReceipt1); + + } +} diff --git a/core/src/test/java/org/platon/core/transaction/TransactionTest.java b/core/src/test/java/org/platon/core/transaction/TransactionTest.java new file mode 100644 index 0000000..d55f487 --- /dev/null +++ b/core/src/test/java/org/platon/core/transaction/TransactionTest.java @@ -0,0 +1,998 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.core.transaction; + +import com.google.protobuf.ByteString; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.platon.core.transaction.proto.ContractDeployRequest; +import org.platon.core.transaction.proto.TransactionType; +import org.platon.core.transaction.proto.defaultRequestData; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.slf4j.Logger; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import static org.junit.Assert.assertEquals; +import static org.slf4j.LoggerFactory.getLogger; + +public class TransactionTest { + private final static Logger logger = getLogger(TransactionTest.class); + + @Test /* build a transaction, sign it and send(using pb), and decode it and verify */ + public void testTransactionFlow() { + byte[] cowPrivKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + ECKey ecKey = ECKey.fromPrivate(cowPrivKey); + + byte[] dataBase = Hex.decode("a9e880872386f26fc1000085e8d4a510008203e89413978aee95f38490e9769c39b2773ed763d9cd5f80"); + defaultRequestData.Builder defReqData = defaultRequestData.newBuilder(); + defReqData.setData(ByteString.copyFrom(dataBase)); + //Any any = Any.pack(defReqData.build()); + byte[] data = defReqData.build().toByteArray(); + //byte[] data = any.toByteArray(); + TransactionType transactionType = TransactionType.TRANSACTION; + BigInteger value = new BigInteger("23"); + byte[] receiveAddress = Hex.decode("f82804881bc16d674ec8000094cd2a3d9f938e13"); + long referenceBlockNum = 101234L; + byte[] referenceBlockHash = HashUtil.sha3(receiveAddress); // + BigInteger energonPrice = new BigInteger("1000000"); + BigInteger energonLimit = new BigInteger("300"); + Integer chainId = 28; + + // next need to read from a file, and do the test list + + Transaction tx = new Transaction(transactionType, value, receiveAddress, referenceBlockNum, + referenceBlockHash, energonPrice, energonLimit, data, chainId); + + tx.sign(ecKey); + + byte[] txEncode = tx.getEncoded(); + Transaction newTx = new Transaction(txEncode); + Assert.assertTrue(transactionType.ordinal() == newTx.getTransactionType().ordinal()); + Assert.assertTrue(value.compareTo(newTx.getValue()) == 0); + Assert.assertArrayEquals(receiveAddress, newTx.getReceiveAddress()); + Assert.assertTrue(referenceBlockNum == newTx.getReferenceBlockNum()); + Assert.assertArrayEquals(referenceBlockHash, newTx.getReferenceBlockHash()); + Assert.assertTrue(energonPrice.compareTo(newTx.getEnergonPrice()) == 0); + Assert.assertTrue(energonLimit.compareTo(newTx.getEnergonLimit()) == 0); + Assert.assertArrayEquals(tx.getData(), newTx.getData()); + + //todo ignore this chainID testify until there is a work rule to generate chainId +// if (chainId != null) +// Assert.assertTrue(chainId == newTx.getChainId()); + + Assert.assertArrayEquals(tx.getRawHash(), newTx.getRawHash()); + Assert.assertArrayEquals(tx.getHash(), newTx.getHash()); + Assert.assertArrayEquals(tx.getSender(), newTx.getSender()); + + tx.verify(); + newTx.verify(); + + Assert.assertTrue(tx.equals(newTx)); + } + + @Test + public void testTransaction1() { + byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + byte[] data = null; + TransactionType transactionType = TransactionType.TRANSACTION; + BigInteger value = BigInteger.ZERO; + byte[] receiveAddress = null; + long referenceBlockNum = 1000; + byte[] referenceBlockHash = HashUtil.sha3(privKey); + BigInteger energonPrice = new BigInteger("20000"); + BigInteger energonLimit = new BigInteger("100"); + Integer chainId = null; + + testTransactionbase(privKey, data, transactionType, + value, receiveAddress, referenceBlockNum,referenceBlockHash, + energonPrice, energonLimit, chainId); + } + + @Test + public void testTransaction1_1() { + byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + byte[] data = null; + TransactionType transactionType = TransactionType.CONTRACT_DEPLOY; + BigInteger value = BigInteger.ZERO; + byte[] receiveAddress = new byte[0]; + long referenceBlockNum = 1000; + byte[] referenceBlockHash = HashUtil.sha3(privKey); + BigInteger energonPrice = new BigInteger("20000"); + BigInteger energonLimit = new BigInteger("100"); + Integer chainId = null; + + testTransactionbase(privKey, data, transactionType, + value, receiveAddress, referenceBlockNum,referenceBlockHash, + energonPrice, energonLimit, chainId); + } + + @Test + public void testTransaction2() { + byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + byte[] data = new byte[0]; + TransactionType transactionType = TransactionType.CONTRACT_DEPLOY; + BigInteger value = BigInteger.ZERO; + byte[] receiveAddress = new byte[0]; + long referenceBlockNum = 1000; + byte[] referenceBlockHash = HashUtil.sha3(privKey); + BigInteger energonPrice = new BigInteger("20000"); + BigInteger energonLimit = new BigInteger("100"); + Integer chainId = null; + + testTransactionbase(privKey, data, transactionType, + value, receiveAddress, referenceBlockNum,referenceBlockHash, + energonPrice, energonLimit, chainId); + } + + @Test + public void testTransaction3() { + byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + TransactionType transactionType = TransactionType.CONTRACT_DEPLOY; + BigInteger value = BigInteger.ZERO; + byte[] receiveAddress = new byte[0]; + long referenceBlockNum = 1000; + byte[] referenceBlockHash = HashUtil.sha3(privKey); + BigInteger energonPrice = new BigInteger("20000"); + BigInteger energonLimit = new BigInteger("100"); + Integer chainId = null; + + String classStr = "mySmartContract.class"; + byte[] className = classStr.getBytes(); + String codeBin = "79573b1a7064c19c1a9819hy79573b1a7064c19c1a981979573b1a7064c19c1a981979573b1a7064c19c1a9819"; + byte[] codeBinary = codeBin.getBytes(); + ContractDeployRequest.Builder newContract = ContractDeployRequest.newBuilder(); + newContract.setClassName(classStr); + newContract.setData(ByteString.copyFrom(codeBinary)); + byte[] data = newContract.build().toByteArray(); + + testTransactionbase(privKey, data, transactionType, + value, receiveAddress, referenceBlockNum,referenceBlockHash, + energonPrice, energonLimit, chainId); + } + + @Test + public void testTransaction4() { + byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + //byte[] data = new byte[0]; + TransactionType transactionType = TransactionType.CONTRACT_DEPLOY; + BigInteger value = BigInteger.ZERO; + byte[] receiveAddress = new byte[0]; + long referenceBlockNum = 1000; + byte[] referenceBlockHash = HashUtil.EMPTY_HASH; + BigInteger energonPrice = new BigInteger("20000"); + BigInteger energonLimit = new BigInteger("100"); + //Integer chainId = null; + + testTransactionbase(privKey, null, transactionType, + value, receiveAddress, referenceBlockNum, referenceBlockHash, + energonPrice, energonLimit, null); + } + +// @Test +// public void testTransaction5() { +// byte[] privKey; +// byte[] data; +// TransactionType transactionType; +// BigInteger value; +// byte[] receiveAddress; +// long referenceBlockNum; +// byte[] referenceBlockHash; +// BigInteger energonPrice; +// BigInteger energonLimit; +// Integer chainId; +// +// testTransactionbase(privKey, data, transactionType, +// value, receiveAddress, referenceBlockNum,referenceBlockHash, +// energonPrice, energonLimit, chainId); +// } + + /* build a transaction, sign it and send(using pb), and decode it and verify */ + public void testTransactionbase(byte[] privKey, byte[] data, TransactionType transactionType, + BigInteger value, byte[] receiveAddress, long referenceBlockNum, byte[] referenceBlockHash, + BigInteger energonPrice, BigInteger energonLimit, Integer chainId) { +// byte[] privKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); +// ECKey ecKey = ECKey.fromPrivate(privKey); +// +// byte[] data = Hex.decode("a9e880872386f26fc1000085e8d4a510008203e89413978aee95f38490e9769c39b2773ed763d9cd5f80"); +// TransactionType transactionType = TransactionType.TRANSACTION; +// BigInteger value = new BigInteger("23"); +// byte[] receiveAddress = Hex.decode("f82804881bc16d674ec8000094cd2a3d9f938e13"); +// long referenceBlockNum = 101234L; +// byte[] referenceBlockHash = HashUtil.sha3(receiveAddress); // +// BigInteger energonPrice = new BigInteger("1000000"); +// BigInteger energonLimit = new BigInteger("300"); +// Integer chainId = 35; + + ECKey ecKey = ECKey.fromPrivate(privKey); + // next need to read from a file, and do the test list + + Transaction tx = new Transaction(transactionType, value, receiveAddress, referenceBlockNum, + referenceBlockHash, energonPrice, energonLimit, data, chainId); + + tx.sign(ecKey); + + byte[] txEncode = tx.getEncoded(); + Transaction newTx = new Transaction(txEncode); + Assert.assertTrue(transactionType.ordinal() == newTx.getTransactionType().ordinal()); + Assert.assertTrue(value.compareTo(newTx.getValue()) == 0); + if(receiveAddress != null && newTx.getReceiveAddress() != null) { + Assert.assertArrayEquals(receiveAddress, newTx.getReceiveAddress()); + } + Assert.assertTrue(referenceBlockNum == newTx.getReferenceBlockNum()); + Assert.assertArrayEquals(referenceBlockHash, newTx.getReferenceBlockHash()); + Assert.assertTrue(energonPrice.compareTo(newTx.getEnergonPrice()) == 0); + Assert.assertTrue(energonLimit.compareTo(newTx.getEnergonLimit()) == 0); + if(data != null && newTx.getData() != null) { + Assert.assertArrayEquals(data, newTx.getData()); + } + if (chainId != null) + Assert.assertTrue(chainId == newTx.getChainId()); + if(transactionType == TransactionType.CONTRACT_DEPLOY) + Assert.assertArrayEquals(tx.getContractAddress(), newTx.getContractAddress()); + + Assert.assertArrayEquals(tx.getRawHash(), newTx.getRawHash()); + Assert.assertArrayEquals(tx.getSender(), newTx.getSender()); + + tx.verify(); + newTx.verify(); + + Assert.assertTrue(tx.equals(newTx)); + } + + @Test /* sign transaction https://tools.ietf.org/html/rfc6979 */ + public void test1() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, IOException { + + //python taken exact data + String txRLPRawData = "a9e880872386f26fc1000085e8d4a510008203e89413978aee95f38490e9769c39b2773ed763d9cd5f80"; + // String txRLPRawData = "f82804881bc16d674ec8000094cd2a3d9f938e13cd947ec05abc7fe734df8dd8268609184e72a0006480"; + + byte[] cowPrivKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + ECKey key = ECKey.fromPrivate(cowPrivKey); + + byte[] data = Hex.decode(txRLPRawData); + + // step 1: serialize + RLP encode + // step 2: hash = keccak(step1) + byte[] txHash = HashUtil.sha3(data); + + String signature = Base64.toBase64String(key.doSign(txHash).toByteArray()); + logger.info(signature); + } + + @Ignore + @Test /* achieve public key of the sender */ + public void test2() throws Exception { + + // cat --> 79b08ad8787060333663d19704909ee7b1903e58 + // cow --> cd2a3d9f938e13cd947ec05abc7fe734df8dd826 + + BigInteger value = new BigInteger("1000000000000000000000"); + + byte[] privKey = HashUtil.sha3("cat".getBytes()); + ECKey ecKey = ECKey.fromPrivate(privKey); + + byte[] senderPrivKey = HashUtil.sha3("cow".getBytes()); + + BigInteger gasPrice = new BigInteger("09184e72a000"); + BigInteger gas = new BigInteger("4255"); + long blockNum = 1000000000L; + byte[] blockHash = Hex.decode("1234567890abcdef1234567890abcdef"); + + // Tn (nonce); Tp(pgas); Tg(gaslimi); Tt(value); Tv(value); Ti(sender); Tw; Tr; Ts + Transaction tx = new Transaction(TransactionType.TRANSACTION, value, ecKey.getAddress(), + blockNum, blockHash, gasPrice, gas, null); + + tx.sign(ECKey.fromPrivate(senderPrivKey)); + + logger.info("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); + logger.info("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); + logger.info("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); + + logger.info("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); + + // retrieve the signer/sender of the transaction + //ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature()); + ECKey key = new ECKey(); + + logger.info("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); + logger.info("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); + + logger.info("Signature public key\t: " + Hex.toHexString(key.getPubKey())); + logger.info("Sender is\t\t: " + Hex.toHexString(key.getAddress())); + + assertEquals("cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + Hex.toHexString(key.getAddress())); + + logger.info(tx.toString()); + } + + +// @Ignore +// @Test /* achieve public key of the sender nonce: 01 */ +// public void test3() throws Exception { +// +// // cat --> 79b08ad8787060333663d19704909ee7b1903e58 +// // cow --> cd2a3d9f938e13cd947ec05abc7fe734df8dd826 +// +// ECKey ecKey = ECKey.fromPrivate(HashUtil.sha3("cat".getBytes())); +// byte[] senderPrivKey = HashUtil.sha3("cow".getBytes()); +// +// byte[] nonce = {0x01}; +// byte[] gasPrice = Hex.decode("09184e72a000"); +// byte[] gasLimit = Hex.decode("4255"); +// BigInteger value = new BigInteger("1000000000000000000000000"); +// +// Transaction tx = new Transaction(nonce, gasPrice, gasLimit, +// ecKey.getAddress(), value.toByteArray(), null); +// +// tx.sign(ECKey.fromPrivate(senderPrivKey)); +// +// logger.info("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); +// logger.info("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); +// logger.info("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); +// +// logger.info("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); +// +// // retrieve the signer/sender of the transaction +// ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature()); +// +// logger.info("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); +// logger.info("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); +// +// logger.info("Signature public key\t: " + Hex.toHexString(key.getPubKey())); +// logger.info("Sender is\t\t: " + Hex.toHexString(key.getAddress())); +// +// assertEquals("cd2a3d9f938e13cd947ec05abc7fe734df8dd826", +// Hex.toHexString(key.getAddress())); +// } +// +// // Testdata from: https://github.com/ethereum/tests/blob/master/txtest.json +// String RLP_ENCODED_RAW_TX = "e88085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080"; +// String RLP_ENCODED_UNSIGNED_TX = "eb8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080808080"; +// String HASH_TX = "328ea6d24659dec48adea1aced9a136e5ebdf40258db30d1b1d97ed2b74be34e"; +// String RLP_ENCODED_SIGNED_TX = "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1"; +// String KEY = "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"; +// byte[] testNonce = Hex.decode(""); +// byte[] testGasPrice = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(1000000000000L)); +// byte[] testGasLimit = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(10000)); +// byte[] testReceiveAddress = Hex.decode("13978aee95f38490e9769c39b2773ed763d9cd5f"); +// byte[] testValue = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(10000000000000000L)); +// byte[] testData = Hex.decode(""); +// byte[] testInit = Hex.decode(""); +// +// @Ignore +// @Test +// public void testTransactionFromSignedRLP() throws Exception { +// Transaction txSigned = new Transaction(Hex.decode(RLP_ENCODED_SIGNED_TX)); +// +// assertEquals(HASH_TX, Hex.toHexString(txSigned.getHash())); +// assertEquals(RLP_ENCODED_SIGNED_TX, Hex.toHexString(txSigned.getEncoded())); +// +// assertEquals(BigInteger.ZERO, new BigInteger(1, txSigned.getNonce())); +// assertEquals(new BigInteger(1, testGasPrice), new BigInteger(1, txSigned.getGasPrice())); +// assertEquals(new BigInteger(1, testGasLimit), new BigInteger(1, txSigned.getGasLimit())); +// assertEquals(Hex.toHexString(testReceiveAddress), Hex.toHexString(txSigned.getReceiveAddress())); +// assertEquals(new BigInteger(1, testValue), new BigInteger(1, txSigned.getValue())); +// assertNull(txSigned.getData()); +// assertEquals(27, txSigned.getSignature().v); +// assertEquals("eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4", Hex.toHexString(BigIntegers.asUnsignedByteArray(txSigned.getSignature().r))); +// assertEquals("14a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", Hex.toHexString(BigIntegers.asUnsignedByteArray(txSigned.getSignature().s))); +// } +// +// @Ignore +// @Test +// public void testTransactionFromUnsignedRLP() throws Exception { +// Transaction txUnsigned = new Transaction(Hex.decode(RLP_ENCODED_UNSIGNED_TX)); +// +// assertEquals(HASH_TX, Hex.toHexString(txUnsigned.getHash())); +// assertEquals(RLP_ENCODED_UNSIGNED_TX, Hex.toHexString(txUnsigned.getEncoded())); +// txUnsigned.sign(ECKey.fromPrivate(Hex.decode(KEY))); +// assertEquals(RLP_ENCODED_SIGNED_TX, Hex.toHexString(txUnsigned.getEncoded())); +// +// assertEquals(BigInteger.ZERO, new BigInteger(1, txUnsigned.getNonce())); +// assertEquals(new BigInteger(1, testGasPrice), new BigInteger(1, txUnsigned.getGasPrice())); +// assertEquals(new BigInteger(1, testGasLimit), new BigInteger(1, txUnsigned.getGasLimit())); +// assertEquals(Hex.toHexString(testReceiveAddress), Hex.toHexString(txUnsigned.getReceiveAddress())); +// assertEquals(new BigInteger(1, testValue), new BigInteger(1, txUnsigned.getValue())); +// assertNull(txUnsigned.getData()); +// assertEquals(27, txUnsigned.getSignature().v); +// assertEquals("eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4", Hex.toHexString(BigIntegers.asUnsignedByteArray(txUnsigned.getSignature().r))); +// assertEquals("14a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", Hex.toHexString(BigIntegers.asUnsignedByteArray(txUnsigned.getSignature().s))); +// } +// +// @Ignore +// @Test +// public void testTransactionFromNew1() throws MissingPrivateKeyException { +// Transaction txNew = new Transaction(testNonce, testGasPrice, testGasLimit, testReceiveAddress, testValue, testData); +// +// assertEquals("", Hex.toHexString(txNew.getNonce())); +// assertEquals(new BigInteger(1, testGasPrice), new BigInteger(1, txNew.getGasPrice())); +// assertEquals(new BigInteger(1, testGasLimit), new BigInteger(1, txNew.getGasLimit())); +// assertEquals(Hex.toHexString(testReceiveAddress), Hex.toHexString(txNew.getReceiveAddress())); +// assertEquals(new BigInteger(1, testValue), new BigInteger(1, txNew.getValue())); +// assertEquals("", Hex.toHexString(txNew.getData())); +// assertNull(txNew.getSignature()); +// +// assertEquals(RLP_ENCODED_RAW_TX, Hex.toHexString(txNew.getEncodedRaw())); +// assertEquals(HASH_TX, Hex.toHexString(txNew.getHash())); +// assertEquals(RLP_ENCODED_UNSIGNED_TX, Hex.toHexString(txNew.getEncoded())); +// txNew.sign(ECKey.fromPrivate(Hex.decode(KEY))); +// assertEquals(RLP_ENCODED_SIGNED_TX, Hex.toHexString(txNew.getEncoded())); +// +// assertEquals(27, txNew.getSignature().v); +// assertEquals("eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4", Hex.toHexString(BigIntegers.asUnsignedByteArray(txNew.getSignature().r))); +// assertEquals("14a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", Hex.toHexString(BigIntegers.asUnsignedByteArray(txNew.getSignature().s))); +// } +// +// @Ignore +// @Test +// public void testTransactionFromNew2() throws MissingPrivateKeyException { +// byte[] privKeyBytes = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); +// +// String RLP_TX_UNSIGNED = "eb8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080808080"; +// String RLP_TX_SIGNED = "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1"; +// String HASH_TX_UNSIGNED = "328ea6d24659dec48adea1aced9a136e5ebdf40258db30d1b1d97ed2b74be34e"; +// +// byte[] nonce = BigIntegers.asUnsignedByteArray(BigInteger.ZERO); +// byte[] gasPrice = Hex.decode("e8d4a51000"); // 1000000000000 +// byte[] gas = Hex.decode("2710"); // 10000 +// byte[] recieveAddress = Hex.decode("13978aee95f38490e9769c39b2773ed763d9cd5f"); +// byte[] value = Hex.decode("2386f26fc10000"); //10000000000000000" +// byte[] data = new byte[0]; +// +// Transaction tx = new Transaction(nonce, gasPrice, gas, recieveAddress, value, data); +// +// // Testing unsigned +// String encodedUnsigned = Hex.toHexString(tx.getEncoded()); +// assertEquals(RLP_TX_UNSIGNED, encodedUnsigned); +// assertEquals(HASH_TX_UNSIGNED, Hex.toHexString(tx.getHash())); +// +// // Testing signed +// tx.sign(ECKey.fromPrivate(privKeyBytes)); +// String encodedSigned = Hex.toHexString(tx.getEncoded()); +// assertEquals(RLP_TX_SIGNED, encodedSigned); +// assertEquals(HASH_TX_UNSIGNED, Hex.toHexString(tx.getHash())); +// } +// +// @Test +// public void testTransactionCreateContract() { +// +//// String rlp = +//// "f89f808609184e72a0008203e8808203e8b84b4560005444602054600f60056002600a02010b0d630000001d596002602054630000003b5860066000530860056006600202010a0d6300000036596004604054630000003b5860056060541ca0ddc901d83110ea50bc40803f42083afea1bbd420548f6392a679af8e24b21345a06620b3b512bea5f0a272703e8d6933177c23afc79516fd0ca4a204aa6e34c7e9"; +// +// byte[] senderPrivKey = HashUtil.sha3("cow".getBytes()); +// +// byte[] nonce = BigIntegers.asUnsignedByteArray(BigInteger.ZERO); +// byte[] gasPrice = Hex.decode("09184e72a000"); // 10000000000000 +// byte[] gas = Hex.decode("03e8"); // 1000 +// byte[] recieveAddress = null; +// byte[] endowment = Hex.decode("03e8"); //10000000000000000" +// byte[] init = Hex.decode +// ("4560005444602054600f60056002600a02010b0d630000001d596002602054630000003b5860066000530860056006600202010a0d6300000036596004604054630000003b586005606054"); +// +// +// Transaction tx1 = new Transaction(nonce, gasPrice, gas, +// recieveAddress, endowment, init); +// tx1.sign(ECKey.fromPrivate(senderPrivKey)); +// +// byte[] payload = tx1.getEncoded(); +// +// +// logger.info(Hex.toHexString(payload)); +// Transaction tx2 = new Transaction(payload); +//// tx2.getSender(); +// +// String plainTx1 = Hex.toHexString(tx1.getEncodedRaw()); +// String plainTx2 = Hex.toHexString(tx2.getEncodedRaw()); +// +//// Transaction tx = new Transaction(Hex.decode(rlp)); +// +// logger.info("tx1.hash: " + Hex.toHexString(tx1.getHash())); +// logger.info("tx2.hash: " + Hex.toHexString(tx2.getHash())); +// logger.info(""); +// logger.info("plainTx1: " + plainTx1); +// logger.info("plainTx2: " + plainTx2); +// +// logger.info(Hex.toHexString(tx2.getSender())); +// } +// +// +// @Ignore +// @Test +// public void encodeReceiptTest() { +// +// String data = "f90244a0f5ff3fbd159773816a7c707a9b8cb6bb778b934a8f6466c7830ed970498f4b688301e848b902000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dbda94cd2a3d9f938e13cd947ec05abc7fe734df8dd826c083a1a1a1"; +// +// byte[] stateRoot = Hex.decode("f5ff3fbd159773816a7c707a9b8cb6bb778b934a8f6466c7830ed970498f4b68"); +// byte[] gasUsed = Hex.decode("01E848"); +// Bloom bloom = new Bloom(Hex.decode("0000000000000000800000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); +// +// LogInfo logInfo1 = new LogInfo( +// Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826"), +// null, +// Hex.decode("a1a1a1") +// ); +// +// List logs = new ArrayList<>(); +// logs.add(logInfo1); +// +// // TODO calculate cumulative gas +// TransactionReceipt receipt = new TransactionReceipt(stateRoot, gasUsed, bloom, logs); +// +// assertEquals(data, +// Hex.toHexString(receipt.getEncoded())); +// } +// +// @Test +// public void constantCallConflictTest() throws Exception { +// /* +// 0x095e7baea6a6c7c4c2dfeb977efac326af552d87 contract is the following Solidity code: +// +// contract Test { +// uint a = 256; +// +// function set(uint s) { +// a = s; +// } +// +// function get() returns (uint) { +// return a; +// } +// } +// */ +// String json = "{ " + +// " 'test1' : { " + +// " 'env' : { " + +// " 'currentCoinbase' : '2adc25665018aa1fe0e6bc666dac8fc2697ff9ba', " + +// " 'currentDifficulty' : '0x0100', " + +// " 'currentGasLimit' : '0x0f4240', " + +// " 'currentNumber' : '0x00', " + +// " 'currentTimestamp' : '0x01', " + +// " 'previousHash' : '5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6' " + +// " }, " + +// " 'logs' : [ " + +// " ], " + +// " 'out' : '0x', " + +// " 'post' : { " + +// " '095e7baea6a6c7c4c2dfeb977efac326af552d87' : { " + +// " 'balance' : '0x0de0b6b3a76586a0', " + +// " 'code' : '0x606060405260e060020a600035046360fe47b1811460245780636d4ce63c14602e575b005b6004356000556022565b6000546060908152602090f3', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " '0x00' : '0x0400' " + +// " } " + +// " }, " + +// " '2adc25665018aa1fe0e6bc666dac8fc2697ff9ba' : { " + +// " 'balance' : '0x67c3', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " } " + +// " }, " + +// " 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b' : { " + +// " 'balance' : '0x0DE0B6B3A762119D', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x01', " + +// " 'storage' : { " + +// " } " + +// " } " + +// " }, " + +// " 'postStateRoot' : '17454a767e5f04461256f3812ffca930443c04a47d05ce3f38940c4a14b8c479', " + +// " 'pre' : { " + +// " '095e7baea6a6c7c4c2dfeb977efac326af552d87' : { " + +// " 'balance' : '0x0de0b6b3a7640000', " + +// " 'code' : '0x606060405260e060020a600035046360fe47b1811460245780636d4ce63c14602e575b005b6004356000556022565b6000546060908152602090f3', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " '0x00' : '0x02' " + +// " } " + +// " }, " + +// " 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b' : { " + +// " 'balance' : '0x0de0b6b3a7640000', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " } " + +// " } " + +// " }, " + +// " 'transaction' : { " + +// " 'data' : '0x60fe47b10000000000000000000000000000000000000000000000000000000000000400', " + +// " 'gasLimit' : '0x061a80', " + +// " 'gasPrice' : '0x01', " + +// " 'nonce' : '0x00', " + +// " 'secretKey' : '45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8', " + +// " 'to' : '095e7baea6a6c7c4c2dfeb977efac326af552d87', " + +// " 'value' : '0x0186a0' " + +// " } " + +// " } " + +// "}"; +// +// StateTestSuite stateTestSuite = new StateTestSuite(json.replaceAll("'", "\"")); +// +// List res = new StateTestRunner(stateTestSuite.getTestCases().get("test1")) { +// @Override +// protected ProgramResult executeTransaction(Transaction tx) { +// // first emulating the constant call (Ethereum.callConstantFunction) +// // to ensure it doesn't affect the final state +// +// { +// Repository track = repository.startTracking(); +// +// Transaction txConst = CallTransaction.createCallTransaction(0, 0, 100000000000000L, +// "095e7baea6a6c7c4c2dfeb977efac326af552d87", 0, CallTransaction.Function.fromSignature("get")); +// txConst.sign(new ECKey()); +// +// Block bestBlock = block; +// +// TransactionExecutor executor = new TransactionExecutor +// (txConst, bestBlock.getCoinbase(), track, new BlockStoreDummy(), +// invokeFactory, bestBlock) +// .setLocalCall(true); +// +// executor.init(); +// executor.execute(); +// executor.go(); +// executor.finalization(); +// +// track.rollback(); +// +// logger.info("Return value: " + new IntType("uint").decode(executor.getResult().getHReturn())); +// } +// +// // now executing the JSON test transaction +// return super.executeTransaction(tx); +// } +// }.runImpl(); +// if (!res.isEmpty()) throw new RuntimeException("Test failed: " + res); +// } +// +// @Test +// public void homesteadContractCreationTest() throws Exception { +// // Checks Homestead updates (1) & (3) from +// // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.mediawiki +// +// /* +// trying to create a contract with the following Solidity code: +// +// contract Test { +// uint a = 256; +// +// function set(uint s) { +// a = s; +// } +// +// function get() returns (uint) { +// return a; +// } +// } +// */ +// +// int iBitLowGas = 0x015f84; // [actual gas required] - 1 +// String aBitLowGas = "0x0" + Integer.toHexString(iBitLowGas); +// String senderPostBalance = "0x0" + Long.toHexString(1000000000000000000L - iBitLowGas); +// +// String json = "{ " + +// " 'test1' : { " + +// " 'env' : { " + +// " 'currentCoinbase' : '2adc25665018aa1fe0e6bc666dac8fc2697ff9ba', " + +// " 'currentDifficulty' : '0x0100', " + +// " 'currentGasLimit' : '0x0f4240', " + +// " 'currentNumber' : '0x01', " + +// " 'currentTimestamp' : '0x01', " + +// " 'previousHash' : '5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6' " + +// " }, " + +// " 'logs' : [ " + +// " ], " + +// " 'out' : '0x', " + +// " 'post' : { " + +// " '2adc25665018aa1fe0e6bc666dac8fc2697ff9ba' : { " + +// " 'balance' : '" + aBitLowGas + "', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " } " + +// " }," + +// " 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b' : { " + +// " 'balance' : '" + senderPostBalance + "', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x01', " + +// " 'storage' : { " + +// " } " + +// " } " + +// " }, " + +// " 'postStateRoot' : '17454a767e5f04461256f3812ffca930443c04a47d05ce3f38940c4a14b8c479', " + +// " 'pre' : { " + +// " 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b' : { " + +// " 'balance' : '0x0de0b6b3a7640000', " + +// " 'code' : '0x', " + +// " 'nonce' : '0x00', " + +// " 'storage' : { " + +// " } " + +// " } " + +// " }, " + +// " 'transaction' : { " + +// " 'data' : '0x6060604052610100600060005055603b8060196000396000f3606060405260e060020a600035046360fe47b1811460245780636d4ce63c14602e575b005b6004356000556022565b6000546060908152602090f3', " + +// " 'gasLimit' : '" + aBitLowGas + "', " + +// " 'gasPrice' : '0x01', " + +// " 'nonce' : '0x00', " + +// " 'secretKey' : '45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8', " + +// " 'to' : '', " + +// " 'value' : '0x0' " + +// " } " + +// " } " + +// "}"; +// +// StateTestSuite stateTestSuite = new StateTestSuite(json.replaceAll("'", "\"")); +// +// logger.info(json.replaceAll("'", "\"")); +// +// try { +// SystemProperties.getDefault().setBlockchainConfig(new HomesteadConfig()); +// List res = new StateTestRunner(stateTestSuite.getTestCases().get("test1")).runImpl(); +// if (!res.isEmpty()) throw new RuntimeException("Test failed: " + res); +// } finally { +// SystemProperties.getDefault().setBlockchainConfig(MainNetConfig.INSTANCE); +// } +// } +// +// @Test +// public void multiSuicideTest() throws IOException, InterruptedException { +// String contract = +// "pragma solidity ^0.4.3;" + +// "contract PsychoKiller {" + +// " function () payable {}" + +// " function homicide() {" + +// " suicide(msg.sender);" + +// " }" + +// " function multipleHomocide() {" + +// " PsychoKiller k = this;" + +// " k.homicide();" + +// " k.homicide();" + +// " k.homicide();" + +// " k.homicide();" + +// " }" + +// "}"; +// SolidityCompiler.Result res = SolidityCompiler.compile( +// contract.getBytes(), true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); +// logger.info(res.errors); +// CompilationResult cres = CompilationResult.parse(res.output); +// +// BlockchainImpl blockchain = ImportLightTest.createBlockchain(GenesisLoader.loadGenesis( +// getClass().getResourceAsStream("/genesis/genesis-light.json"))); +// +// ECKey sender = ECKey.fromPrivate(Hex.decode("3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c")).compress(); +// logger.info("address: " + Hex.toHexString(sender.getAddress())); +// +// if (cres.getContract("PsychoKiller") != null) { +// Transaction tx = createTx(blockchain, sender, new byte[0], +// Hex.decode(cres.getContract("PsychoKiller").bin)); +// executeTransaction(blockchain, tx); +// +// byte[] contractAddress = tx.getContractAddress(); +// +// CallTransaction.Contract contract1 = new CallTransaction.Contract(cres.getContract("PsychoKiller").abi); +// byte[] callData = contract1.getByName("multipleHomocide").encode(); +// +// Transaction tx1 = createTx(blockchain, sender, contractAddress, callData, 0l); +// ProgramResult programResult = executeTransaction(blockchain, tx1).getResult(); +// +// // suicide of a single account should be counted only once +// Assert.assertEquals(24000, programResult.getFutureRefund()); +// } else { +// Assert.fail(); +// } +// } +// +// @Test +// public void receiptErrorTest() throws Exception { +// BlockchainImpl blockchain = ImportLightTest.createBlockchain(GenesisLoader.loadGenesis( +// getClass().getResourceAsStream("/genesis/genesis-light.json"))); +// +// ECKey sender = ECKey.fromPrivate(Hex.decode("3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c")); +// +// { +// // Receipt RLP backward compatibility +// TransactionReceipt receipt = new TransactionReceipt(Hex.decode("f9010c80825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c082520880")); +// +// Assert.assertTrue(receipt.isValid()); +// Assert.assertTrue(receipt.isSuccessful()); +// } +// +// { +// Transaction tx = createTx(blockchain, sender, new byte[32], new byte[0], 100); +// TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); +// +// logger.info(Hex.toHexString(receipt.getEncoded())); +// +// receipt = new TransactionReceipt(receipt.getEncoded()); +// +// Assert.assertTrue(receipt.isValid()); +// Assert.assertTrue(receipt.isSuccessful()); +// } +// +// { +// Transaction tx = createTx(blockchain, new ECKey(), new byte[32], new byte[0], 100); +// TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); +// +// receipt = new TransactionReceipt(receipt.getEncoded()); +// +// Assert.assertFalse(receipt.isValid()); +// Assert.assertFalse(receipt.isSuccessful()); +// Assert.assertTrue(receipt.getError().contains("Not enough")); +// } +// +// { +// Transaction tx = new Transaction( +// ByteUtil.intToBytesNoLeadZeroes(100500), +// ByteUtil.longToBytesNoLeadZeroes(1), +// ByteUtil.longToBytesNoLeadZeroes(3_000_000), +// new byte[0], +// ByteUtil.longToBytesNoLeadZeroes(0), +// new byte[0]); +// tx.sign(sender); +// TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); +// receipt = new TransactionReceipt(receipt.getEncoded()); +// Assert.assertFalse(receipt.isValid()); +// Assert.assertFalse(receipt.isSuccessful()); +// Assert.assertTrue(receipt.getError().contains("nonce")); +// } +// +// { +// String contract = +// "contract GasConsumer {" + +// " function GasConsumer() {" + +// " int i = 0;" + +// " while(true) sha3(i++);" + +// " }" + +// "}"; +// SolidityCompiler.Result res = SolidityCompiler.compile( +// contract.getBytes(), true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); +// logger.info(res.errors); +// CompilationResult cres = CompilationResult.parse(res.output); +// Transaction tx = createTx(blockchain, sender, new byte[0], Hex.decode(cres.getContract("GasConsumer").bin), 0); +// TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); +// receipt = new TransactionReceipt(receipt.getEncoded()); +// +// Assert.assertTrue(receipt.isValid()); +// Assert.assertFalse(receipt.isSuccessful()); +// Assert.assertTrue(receipt.getError().contains("Not enough gas")); +// } +// } +// +// private Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, byte[] data) { +// return createTx(blockchain, sender, receiveAddress, data, 0); +// } +// private Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, +// byte[] data, long value) { +// BigInteger nonce = blockchain.getRepository().getNonce(sender.getAddress()); +// Transaction tx = new Transaction( +// ByteUtil.bigIntegerToBytes(nonce), +// ByteUtil.longToBytesNoLeadZeroes(0), +// ByteUtil.longToBytesNoLeadZeroes(3_000_000), +// receiveAddress, +// ByteUtil.longToBytesNoLeadZeroes(value), +// data); +// tx.sign(sender); +// return tx; +// } +// +// private TransactionExecutor executeTransaction(BlockchainImpl blockchain, Transaction tx) { +// Repository track = blockchain.getRepository().startTracking(); +// TransactionExecutor executor = new TransactionExecutor(tx, new byte[32], blockchain.getRepository(), +// blockchain.getBlockStore(), blockchain.getProgramInvokeFactory(), blockchain.getBestBlock()); +// +// executor.init(); +// executor.execute(); +// executor.go(); +// executor.finalization(); +// +// track.commit(); +// return executor; +// } +// +// @Test +// public void afterEIP158Test() throws Exception { +// int chainId = 1; +// String rlpUnsigned = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080"; +// String unsignedHash = "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"; +// String privateKey = "4646464646464646464646464646464646464646464646464646464646464646"; +// BigInteger signatureR = new BigInteger("18515461264373351373200002665853028612451056578545711640558177340181847433846"); +// BigInteger signatureS = new BigInteger("46948507304638947509940763649030358759909902576025900602547168820602576006531"); +// String signedTxRlp = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; +// +// Transaction tx = Transaction.create( +// "3535353535353535353535353535353535353535", +// new BigInteger("1000000000000000000"), +// new BigInteger("9"), +// new BigInteger("20000000000"), +// new BigInteger("21000"), +// chainId +// ); +// +// // Checking RLP of unsigned transaction and its hash +// assertArrayEquals(Hex.decode(rlpUnsigned), tx.getEncoded()); +// assertArrayEquals(Hex.decode(unsignedHash), tx.getHash()); +// assertNull(tx.getSignature()); +// +// ECKey ecKey = ECKey.fromPrivate(Hex.decode(privateKey)); +// tx.sign(ecKey); +// +// // Checking modified signature +// assertEquals(signatureR, tx.getSignature().r); +// assertEquals(signatureS, tx.getSignature().s); +// // TODO: Strange, it's still 27. Why is signature used for getEncoded() never assigned to signature field? +// assertEquals(27, tx.getSignature().v); +// +// // Checking that we get correct TX in the end +// assertArrayEquals(Hex.decode(signedTxRlp), tx.getEncoded()); +// +// // Check that we could correctly extract tx from new RLP +// Transaction txSigned = new Transaction(Hex.decode(signedTxRlp)); +// assertEquals((int)txSigned.getChainId(), chainId); +// } +// +// @Test +// public void etcChainIdTest() { +// Transaction tx = new Transaction(Hex.decode("f871830617428504a817c80083015f90940123286bd94beecd40905321f5c3202c7628d685880ecab7b2bae2c27080819ea021355678b1aa704f6ad4706fb8647f5125beadd1d84c6f9cf37dda1b62f24b1aa06b4a64fd29bb6e54a2c5107e8be42ac039a8ffb631e16e7bcbd15cdfc0015ee2")); +// Integer chainId = tx.getChainId(); +// assertEquals(61, chainId.intValue()); +// } +// +// @Test +// public void longChainIdTest() { +// Transaction tx = new Transaction(Hex.decode("f8ae82477b8504a817c80083015f9094977ddf44438d540892d1b8618fea65395399971680b844eceb6e3e57696e6454757262696e655f30310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007827e19a025f55532f5cebec362f3f750a3b9c47ab76322622eb3a26ad24c80f9c388c15ba02dcc7ebcfb6ad6ae09f56a29d710cc4115e960a83b98405cf98f7177c14d8a51")); +// Integer chainId = tx.getChainId(); +// assertEquals(16123, chainId.intValue()); +// +// Transaction tx1 = Transaction.create( +// "3535353535353535353535353535353535353535", +// new BigInteger("1000000000000000000"), +// new BigInteger("9"), +// new BigInteger("20000000000"), +// new BigInteger("21000"), +// 333333 +// ); +// +// ECKey key = new ECKey(); +// tx1.sign(key); +// +// Transaction tx2 = new Transaction(tx1.getEncoded()); +// assertEquals(333333, tx2.getChainId().intValue()); +// assertArrayEquals(tx2.getSender(), key.getAddress()); +// +// } +// +// @Test +// public void unsignedChainIdTransactionTest() { +// byte[] rlpUnsignedTx = Hex.decode("ef098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080830516158080"); +// Transaction tx = new Transaction(rlpUnsignedTx); +// assertEquals(333333, (long) tx.getChainId()); +// Transaction copyTx = new Transaction(tx.getNonce(), tx.getGasPrice(), tx.getGasLimit(), tx.getReceiveAddress(), tx.getValue(), tx.getData(), tx.getChainId()); +// assertArrayEquals(rlpUnsignedTx, copyTx.getEncoded()); +// } +} diff --git a/core/src/test/java/org/platon/core/validator/TimelinessValidatorTest.java b/core/src/test/java/org/platon/core/validator/TimelinessValidatorTest.java new file mode 100644 index 0000000..5d028ca --- /dev/null +++ b/core/src/test/java/org/platon/core/validator/TimelinessValidatorTest.java @@ -0,0 +1,67 @@ +package org.platon.core.validator; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.core.validator.model.ValidateBlock; + +import java.util.HashSet; + +import static org.junit.Assert.*; + +public class TimelinessValidatorTest { + + private static final byte[] blockHash = "blockHash".getBytes(); + private static final long ancestorBlockNum = 1L; + private static final byte[] ancestorBlockHash ="ancestorBlockHash".getBytes(); + private static final byte[] txHash ="txHash".getBytes(); + private static final byte[] txHash2 = "txHash2".getBytes(); + private HashSet txSet; + + + @Test + public void validateBlockHash() { + TimelinessValidator timelinessValidator = this.addHash(); + Assert.assertTrue(timelinessValidator.validateBlockHash(blockHash,ancestorBlockNum,ancestorBlockHash)); + Assert.assertFalse(timelinessValidator.validateBlockHash(blockHash,ancestorBlockNum+1,ancestorBlockHash)); + } + + @Test + public void addBlockHash() { + TimelinessValidator timelinessValidator = this.addHash(); + Assert.assertTrue(timelinessValidator.addBlockHash(blockHash,ancestorBlockNum,ancestorBlockHash,null)); + Assert.assertFalse(timelinessValidator.addBlockHash(blockHash,ancestorBlockNum,ancestorBlockHash,null)); + } + + @Test + public void addAncestorBlockHash() { + TimelinessValidator timelinessValidator = this.addHash(); + Assert.assertNotNull(timelinessValidator); + } + + private TimelinessValidator addHash(){ + TimelinessValidator timelinessValidator = new TimelinessValidator(); + txSet = new HashSet<>(); + txSet.add(new ByteArrayWrapper(txHash)); + if(!timelinessValidator.addAncestorBlockHash(ancestorBlockHash,ancestorBlockNum,null,0,txSet)){ + return null; + } + return timelinessValidator; + } + + @Test + public void validateTxHash() { + TimelinessValidator timelinessValidator = this.addHash(); + Assert.assertTrue(timelinessValidator.validateTxHash(txHash2,ancestorBlockNum,ancestorBlockHash)); + Assert.assertFalse(timelinessValidator.validateTxHash(txHash,ancestorBlockNum,ancestorBlockHash)); + } + + @Test + public void addTxHash() { + TimelinessValidator timelinessValidator = this.addHash(); + Assert.assertTrue(timelinessValidator.addTxHash(txHash2,ancestorBlockNum,ancestorBlockHash)); + Assert.assertFalse(timelinessValidator.addTxHash(txHash2,ancestorBlockNum,ancestorBlockHash)); + } + +} \ No newline at end of file diff --git a/core/src/test/resources/config/genesis.json b/core/src/test/resources/config/genesis.json new file mode 100644 index 0000000..c45bc39 --- /dev/null +++ b/core/src/test/resources/config/genesis.json @@ -0,0 +1,16 @@ +{ + "config": { + "consensus": "Noproof", + "energonLimit": "1388" + }, + "accounts": { + "fG58UzwfKzAVXwYNaNUvkLSEPqYSkR25o": { + "balance": "10000000000000000000000" + } + }, + "autor": "5f2ae1c60a3038956cf3355960cb211de78bab50", + "coinbase" : "5f2ae1c60a3038956cf3355960cb211de78bab50", + "timestamp": "1533872936173", + "parentHash": "0000000000000000000000000000000000000000000000000000000000000000", + "energonLimit": "1388" +} \ No newline at end of file diff --git a/crypto/build.gradle b/crypto/build.gradle new file mode 100644 index 0000000..217e7cc --- /dev/null +++ b/crypto/build.gradle @@ -0,0 +1,18 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile project(":common") + compile "org.bouncycastle:bcprov-jdk15on:1.59" + compile "com.alibaba:fastjson:1.2.29" // fastjson + compile group: 'commons-lang', name: 'commons-lang', version:'2.4' + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +task createJavaProject << { + sourceSets*.java.srcDirs*.each{ it.mkdirs() } + sourceSets*.resources.srcDirs*.each{ it.mkdirs()} +} \ No newline at end of file diff --git a/crypto/src/main/java/org/platon/crypto/Base58.java b/crypto/src/main/java/org/platon/crypto/Base58.java new file mode 100644 index 0000000..9178602 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/Base58.java @@ -0,0 +1,190 @@ +/* + * Copyright 2011 Google Inc. + * Copyright 2018 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.platon.crypto; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * Base58 is a way to encode PlatON addresses + */ +public class Base58 { + private static final Logger logger = LoggerFactory.getLogger(HashUtil.class); + + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final char ENCODED_ZERO = ALPHABET[0]; + private static final int[] INDEXES = new int[128]; + static { + Arrays.fill(INDEXES, -1); + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes as a base58 string + * @param input the bytes to encode + * @return the base58-encoded string + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input.length && input[zeros] == 0) { + ++zeros; + } + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + input = Arrays.copyOf(input, input.length); // since we modify it in-place + char[] encoded = new char[input.length * 2]; // upper bound + int outputStart = encoded.length; + for (int inputStart = zeros; inputStart < input.length; ) { + encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; + if (input[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return new String(encoded, outputStart, encoded.length - outputStart); + } + + /** + * Encodes the given version and bytes as a base58 string. + * @param version the version to encode + * @param payload the bytes to encode + * @return the base58-encoded string + */ + public static String encodeChecked(int version, byte[] payload) { + if (version < 0 || version > 255){ + throw new IllegalArgumentException("Version not in range."); + } + // 1 byte version + data bytes + 4 bytes check code (a truncated hash) + byte[] addressBytes = new byte[1 + payload.length + 4]; + addressBytes[0] = (byte) version; + System.arraycopy(payload, 0, addressBytes, 1, payload.length); + byte[] checksum = HashUtil.sha3Twice(addressBytes, 0, payload.length + 1); + System.arraycopy(checksum, 0, addressBytes, payload.length + 1, 4); + return Base58.encode(addressBytes); + } + + /** + * Decodes the given base58 string into the original data bytes. + * + * @param input the base58-encoded string to decode + * @return the decoded data bytes + */ + public static byte[] decode(String input){ + if (input.length() == 0) { + return new byte[0]; + } + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + byte[] input58 = new byte[input.length()]; + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + int digit = c < 128 ? INDEXES[c] : -1; + if (digit < 0) { + logger.error("the input is not base 58 string"); + return null; + } + input58[i] = (byte) digit; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + ++zeros; + } + // Convert base-58 digits to base-256 digits. + byte[] decoded = new byte[input.length()]; + int outputStart = decoded.length; + for (int inputStart = zeros; inputStart < input58.length; ) { + decoded[--outputStart] = divmod(input58, inputStart, 58, 256); + if (input58[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.length && decoded[outputStart] == 0) { + ++outputStart; + } + // Return decoded data (including original number of leading zeros). + return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); + } + + public static BigInteger decodeToBigInteger(String input){ + return new BigInteger(1, decode(input)); + } + + /** + * Decodes the given base58 string into the original data bytes, using the checksum in the + * last 4 bytes of the decoded data to verify that the rest are correct. The checksum is + * removed from the returned data. + * + * @param input the base58-encoded string to decode (which should include the checksum) + */ + public static byte[] decodeChecked(String input){ + byte[] decoded = decode(input); + if (decoded.length < 4){ + logger.error("Input too short: " + decoded.length); + return null; + } + byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4); + byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length); + byte[] actualChecksum = Arrays.copyOfRange(HashUtil.sha3Twice(data), 0, 4); + if (!Arrays.equals(checksum, actualChecksum)){ + logger.error("InvalidChecksum"); + return null; + } + return data; + } + + /** + * Divides a number, represented as an array of bytes each containing a single digit + * in the specified base, by the given divisor. The given number is modified in-place + * to contain the quotient, and the return value is the remainder. + * + * @param number the number to divide + * @param firstDigit the index within the array of the first non-zero digit + * (this is used for optimization by skipping the leading zeros) + * @param base the base in which the number's digits are represented (up to 256) + * @param divisor the number to divide by (up to 256) + * @return the remainder of the division operation + */ + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + // this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; + } + return (byte) remainder; + } + +} diff --git a/crypto/src/main/java/org/platon/crypto/CheckUtil.java b/crypto/src/main/java/org/platon/crypto/CheckUtil.java new file mode 100644 index 0000000..f962ccb --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/CheckUtil.java @@ -0,0 +1,8 @@ +package org.platon.crypto; + +public class CheckUtil { + + public static void check(boolean test, String message) { + if (!test) throw new IllegalArgumentException(message); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/ConcatKDFBytesGenerator.java b/crypto/src/main/java/org/platon/crypto/ConcatKDFBytesGenerator.java new file mode 100644 index 0000000..bb40160 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/ConcatKDFBytesGenerator.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.DigestDerivationFunction; +import org.bouncycastle.crypto.params.ISO18033KDFParameters; +import org.bouncycastle.crypto.params.KDFParameters; +import org.bouncycastle.util.Pack; + +/** + * Basic KDF generator for derived keys and ivs as defined by NIST SP 800-56A. + */ +public class ConcatKDFBytesGenerator + implements DigestDerivationFunction +{ + private int counterStart; + private Digest digest; + private byte[] shared; + private byte[] iv; + + /** + * Construct a KDF Parameters generator. + *

+ * + * @param counterStart + * value of counter. + * @param digest + * the digest to be used as the source of derived keys. + */ + protected ConcatKDFBytesGenerator(int counterStart, Digest digest) + { + this.counterStart = counterStart; + this.digest = digest; + } + + public ConcatKDFBytesGenerator(Digest digest) { + this(1, digest); + } + + public void init(DerivationParameters param) + { + if (param instanceof KDFParameters) + { + KDFParameters p = (KDFParameters)param; + + shared = p.getSharedSecret(); + iv = p.getIV(); + } + else if (param instanceof ISO18033KDFParameters) + { + ISO18033KDFParameters p = (ISO18033KDFParameters)param; + + shared = p.getSeed(); + iv = null; + } + else + { + throw new IllegalArgumentException("KDF parameters required for KDF2Generator"); + } + } + + /** + * return the underlying digest. + */ + public Digest getDigest() + { + return digest; + } + + /** + * fill len bytes of the output buffer with bytes generated from the + * derivation function. + * + * @throws IllegalArgumentException + * if the size of the request will cause an overflow. + * @throws DataLengthException + * if the out buffer is too small. + */ + public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException, + IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + long oBytes = len; + int outLen = digest.getDigestSize(); + + // + // this is at odds with the standard implementation, the + // maximum value should be hBits * (2^32 - 1) where hBits + // is the digest output size in bits. We can't have an + // array with a long index at the moment... + // + if (oBytes > ((2L << 32) - 1)) + { + throw new IllegalArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.getDigestSize()]; + + byte[] C = new byte[4]; + Pack.intToBigEndian(counterStart, C, 0); + + int counterBase = counterStart & ~0xFF; + + for (int i = 0; i < cThreshold; i++) + { + digest.update(C, 0, C.length); + digest.update(shared, 0, shared.length); + + if (iv != null) + { + digest.update(iv, 0, iv.length); + } + + digest.doFinal(dig, 0); + + if (len > outLen) + { + System.arraycopy(dig, 0, out, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + System.arraycopy(dig, 0, out, outOff, len); + } + + if (++C[3] == 0) + { + counterBase += 0x100; + Pack.intToBigEndian(counterBase, C, 0); + } + } + + digest.reset(); + + return (int)oBytes; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/ECIESCoder.java b/crypto/src/main/java/org/platon/crypto/ECIESCoder.java new file mode 100644 index 0000000..cb2e66f --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/ECIESCoder.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto; + +import com.google.common.base.Throwables; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.KeyGenerationParameters; +import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.modes.SICBlockCipher; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.math.ec.ECPoint; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; + +import static org.platon.crypto.ECKey.CURVE; + +public class ECIESCoder { + + + public static final int KEY_SIZE = 128; + + public static byte[] decrypt(BigInteger privKey, byte[] cipher) throws IOException, InvalidCipherTextException { + return decrypt(privKey, cipher, null); + } + + public static byte[] decrypt(BigInteger privKey, byte[] cipher, byte[] macData) throws IOException, InvalidCipherTextException { + + byte[] plaintext; + + ByteArrayInputStream is = new ByteArrayInputStream(cipher); + byte[] ephemBytes = new byte[2*((CURVE.getCurve().getFieldSize()+7)/8) + 1]; + + is.read(ephemBytes); + ECPoint ephem = CURVE.getCurve().decodePoint(ephemBytes); + byte[] IV = new byte[KEY_SIZE /8]; + is.read(IV); + byte[] cipherBody = new byte[is.available()]; + is.read(cipherBody); + + plaintext = decrypt(ephem, privKey, IV, cipherBody, macData); + + return plaintext; + } + + public static byte[] decrypt(ECPoint ephem, BigInteger prv, byte[] IV, byte[] cipher, byte[] macData) throws InvalidCipherTextException { + AESEngine aesFastEngine = new AESEngine(); + + ECIESEngine iesEngine = new ECIESEngine( + new ECDHBasicAgreement(), + new ConcatKDFBytesGenerator(new SHA256Digest()), + new HMac(new SHA256Digest()), + new SHA256Digest(), + new BufferedBlockCipher(new SICBlockCipher(aesFastEngine))); + + + byte[] d = new byte[] {}; + byte[] e = new byte[] {}; + + IESParameters p = new IESWithCipherParameters(d, e, KEY_SIZE, KEY_SIZE); + ParametersWithIV parametersWithIV = + new ParametersWithIV(p, IV); + + iesEngine.init(false, new ECPrivateKeyParameters(prv, CURVE), new ECPublicKeyParameters(ephem, CURVE), parametersWithIV); + + return iesEngine.processBlock(cipher, 0, cipher.length, macData); + } + + public static byte[] encrypt(ECPoint toPub, byte[] plaintext) { + return encrypt(toPub, plaintext, null); + } + + public static byte[] encrypt(ECPoint toPub, byte[] plaintext, byte[] macData) { + + ECKeyPairGenerator eGen = new ECKeyPairGenerator(); + SecureRandom random = new SecureRandom(); + KeyGenerationParameters gParam = new ECKeyGenerationParameters(CURVE, random); + + eGen.init(gParam); + + byte[] IV = new byte[KEY_SIZE/8]; + new SecureRandom().nextBytes(IV); + + AsymmetricCipherKeyPair ephemPair = eGen.generateKeyPair(); + BigInteger prv = ((ECPrivateKeyParameters)ephemPair.getPrivate()).getD(); + ECPoint pub = ((ECPublicKeyParameters)ephemPair.getPublic()).getQ(); + ECIESEngine iesEngine = makeIESEngine(true, toPub, prv, IV); + + + ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(CURVE, random); + ECKeyPairGenerator generator = new ECKeyPairGenerator(); + generator.init(keygenParams); + + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters(ECKey.CURVE, random)); + + byte[] cipher; + try { + cipher = iesEngine.processBlock(plaintext, 0, plaintext.length, macData); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(pub.getEncoded(false)); + bos.write(IV); + bos.write(cipher); + return bos.toByteArray(); + } catch (InvalidCipherTextException e) { + throw Throwables.propagate(e); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + /** + * Encryption equivalent to the Crypto++ default ECIES settings: + * + * DL_KeyAgreementAlgorithm: DL_KeyAgreementAlgorithm_DH > + * DL_KeyDerivationAlgorithm: DL_KeyDerivationAlgorithm_P1363 > + * DL_SymmetricEncryptionAlgorithm: DL_EncryptionAlgorithm_Xor,0> + * DL_PrivateKey: DL_Key + * DL_PrivateKey_EC + * + * Used for Whisper V3 + */ + public static byte[] encryptSimple(ECPoint pub, byte[] plaintext) throws IOException, InvalidCipherTextException { + ECIESEngine iesEngine = new ECIESEngine( + new ECDHBasicAgreement(), + new MGF1BytesGeneratorExt(new SHA1Digest(), 1), + new HMac(new SHA1Digest()), + new SHA1Digest(), + null); + + IESParameters p = new IESParameters(null, null, KEY_SIZE); + ParametersWithIV parametersWithIV = new ParametersWithIV(p, new byte[0]); + + iesEngine.setHashMacKey(false); + + ECKeyPairGenerator eGen = new ECKeyPairGenerator(); + SecureRandom random = new SecureRandom(); + KeyGenerationParameters gParam = new ECKeyGenerationParameters(CURVE, random); + eGen.init(gParam); + +// AsymmetricCipherKeyPairGenerator testGen = new AsymmetricCipherKeyPairGenerator() { +// ECKey priv = ECKey.fromPrivate(Hex.decode("d0b043b4c5d657670778242d82d68a29d25d7d711127d17b8e299f156dad361a")); +// +// @Override +// public void init(KeyGenerationParameters keyGenerationParameters) { +// } +// +// @Override +// public AsymmetricCipherKeyPair generateKeyPair() { +// return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(priv.getPubKeyPoint(), CURVE), +// new ECPrivateKeyParameters(priv.getPrivKey(), CURVE)); +// } +// }; + + EphemeralKeyPairGenerator ephemeralKeyPairGenerator = + new EphemeralKeyPairGenerator(/*testGen*/eGen, new ECIESPublicKeyEncoder()); + + iesEngine.init(new ECPublicKeyParameters(pub, CURVE), parametersWithIV, ephemeralKeyPairGenerator); + + return iesEngine.processBlock(plaintext, 0, plaintext.length); + } + + + private static ECIESEngine makeIESEngine(boolean isEncrypt, ECPoint pub, BigInteger prv, byte[] IV) { + AESEngine aesFastEngine = new AESEngine(); + + ECIESEngine iesEngine = new ECIESEngine( + new ECDHBasicAgreement(), + new ConcatKDFBytesGenerator(new SHA256Digest()), + new HMac(new SHA256Digest()), + new SHA256Digest(), + new BufferedBlockCipher(new SICBlockCipher(aesFastEngine))); + + + byte[] d = new byte[] {}; + byte[] e = new byte[] {}; + + IESParameters p = new IESWithCipherParameters(d, e, KEY_SIZE, KEY_SIZE); + ParametersWithIV parametersWithIV = new ParametersWithIV(p, IV); + + iesEngine.init(isEncrypt, new ECPrivateKeyParameters(prv, CURVE), new ECPublicKeyParameters(pub, CURVE), parametersWithIV); + return iesEngine; + } + + public static int getOverhead() { + // 256 bit EC public key, IV, 256 bit MAC + return 65 + KEY_SIZE/8 + 32; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/ECIESEngine.java b/crypto/src/main/java/org/platon/crypto/ECIESEngine.java new file mode 100644 index 0000000..2f28b6a --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/ECIESEngine.java @@ -0,0 +1,463 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto; + +import org.bouncycastle.crypto.*; +import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Pack; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; + +/** + * Support class for constructing integrated encryption cipher + * for doing basic message exchanges on top of key agreement ciphers. + * Follows the description given in IEEE Std 1363a with a couple of changes + * specific to Ethereum: + * - Hash the MAC key before use + * - Include the encryption IV in the MAC computation + */ +public class ECIESEngine +{ + private final Digest hash; + BasicAgreement agree; + DerivationFunction kdf; + Mac mac; + BufferedBlockCipher cipher; + byte[] macBuf; + + boolean forEncryption; + CipherParameters privParam, pubParam; + IESParameters param; + + byte[] V; + private EphemeralKeyPairGenerator keyPairGenerator; + private KeyParser keyParser; + private byte[] IV; + boolean hashK2 = true; + + /** + * set up for use with stream mode, where the key derivation function + * is used to provide a stream of bytes to xor with the message. + * @param agree the key agreement used as the basis for the encryption + * @param kdf the key derivation function used for byte generation + * @param mac the message authentication code generator for the message + * @param hash hash ing function + * @param cipher the actual cipher + */ + public ECIESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac, Digest hash, BufferedBlockCipher cipher) + { + this.agree = agree; + this.kdf = kdf; + this.mac = mac; + this.hash = hash; + this.macBuf = new byte[mac.getMacSize()]; + this.cipher = cipher; + } + + + public void setHashMacKey(boolean hashK2) { + this.hashK2 = hashK2; + } + + /** + * Initialise the encryptor. + * + * @param forEncryption whether or not this is encryption/decryption. + * @param privParam our private key parameters + * @param pubParam the recipient's/sender's public key parameters + * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. + */ + public void init( + boolean forEncryption, + CipherParameters privParam, + CipherParameters pubParam, + CipherParameters params) + { + this.forEncryption = forEncryption; + this.privParam = privParam; + this.pubParam = pubParam; + this.V = new byte[0]; + + extractParams(params); + } + + + /** + * Initialise the encryptor. + * + * @param publicKey the recipient's/sender's public key parameters + * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. + * @param ephemeralKeyPairGenerator the ephemeral key pair generator to use. + */ + public void init(AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator) + { + this.forEncryption = true; + this.pubParam = publicKey; + this.keyPairGenerator = ephemeralKeyPairGenerator; + + extractParams(params); + } + + /** + * Initialise the encryptor. + * + * @param privateKey the recipient's private key. + * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. + * @param publicKeyParser the parser for reading the ephemeral public key. + */ + public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser) + { + this.forEncryption = false; + this.privParam = privateKey; + this.keyParser = publicKeyParser; + + extractParams(params); + } + + private void extractParams(CipherParameters params) + { + if (params instanceof ParametersWithIV) + { + this.IV = ((ParametersWithIV)params).getIV(); + this.param = (IESParameters)((ParametersWithIV)params).getParameters(); + } + else + { + this.IV = null; + this.param = (IESParameters)params; + } + } + + public BufferedBlockCipher getCipher() + { + return cipher; + } + + public Mac getMac() + { + return mac; + } + + private byte[] encryptBlock( + byte[] in, + int inOff, + int inLen, + byte[] macData) + throws InvalidCipherTextException + { + byte[] C = null, K = null, K1 = null, K2 = null; + int len; + + if (cipher == null) + { + // Streaming mode. + K1 = new byte[inLen]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + +// if (V.length != 0) +// { +// System.arraycopy(K, 0, K2, 0, K2.length); +// System.arraycopy(K, K2.length, K1, 0, K1.length); +// } +// else + { + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, inLen, K2, 0, K2.length); + } + + C = new byte[inLen]; + + for (int i = 0; i != inLen; i++) + { + C[i] = (byte)(in[inOff + i] ^ K1[i]); + } + len = inLen; + } + else + { + // Block cipher mode. + K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + + // If iv provided use it to initialise the cipher + if (IV != null) + { + cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV)); + } + else + { + cipher.init(true, new KeyParameter(K1)); + } + + C = new byte[cipher.getOutputSize(inLen)]; + len = cipher.processBytes(in, inOff, inLen, C, 0); + len += cipher.doFinal(C, len); + } + + + // Convert the length of the encoding vector into a byte array. + byte[] P2 = param.getEncodingV(); + + // Apply the MAC. + byte[] T = new byte[mac.getMacSize()]; + + byte[] K2a; + if (hashK2) { + K2a = new byte[hash.getDigestSize()]; + hash.reset(); + hash.update(K2, 0, K2.length); + hash.doFinal(K2a, 0); + } else { + K2a = K2; + } + mac.init(new KeyParameter(K2a)); + mac.update(IV, 0, IV.length); + mac.update(C, 0, C.length); + if (P2 != null) + { + mac.update(P2, 0, P2.length); + } + if (V.length != 0 && P2 != null) { + byte[] L2 = new byte[4]; + Pack.intToBigEndian(P2.length * 8, L2, 0); + mac.update(L2, 0, L2.length); + } + + if (macData != null) { + mac.update(macData, 0, macData.length); + } + + mac.doFinal(T, 0); + + // Output the triple (V,C,T). + byte[] Output = new byte[V.length + len + T.length]; + System.arraycopy(V, 0, Output, 0, V.length); + System.arraycopy(C, 0, Output, V.length, len); + System.arraycopy(T, 0, Output, V.length + len, T.length); + return Output; + } + + private byte[] decryptBlock( + byte[] in_enc, + int inOff, + int inLen, + byte[] macData) + throws InvalidCipherTextException + { + byte[] M = null, K = null, K1 = null, K2 = null; + int len; + + // Ensure that the length of the input is greater than the MAC in bytes + if (inLen <= (param.getMacKeySize() / 8)) + { + throw new InvalidCipherTextException("Length of input must be greater than the MAC"); + } + + if (cipher == null) + { + // Streaming mode. + K1 = new byte[inLen - V.length - mac.getMacSize()]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + +// if (V.length != 0) +// { +// System.arraycopy(K, 0, K2, 0, K2.length); +// System.arraycopy(K, K2.length, K1, 0, K1.length); +// } +// else + { + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + } + + M = new byte[K1.length]; + + for (int i = 0; i != K1.length; i++) + { + M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]); + } + + len = K1.length; + } + else + { + // Block cipher mode. + K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; + K2 = new byte[param.getMacKeySize() / 8]; + K = new byte[K1.length + K2.length]; + + kdf.generateBytes(K, 0, K.length); + System.arraycopy(K, 0, K1, 0, K1.length); + System.arraycopy(K, K1.length, K2, 0, K2.length); + + // If IV provide use it to initialize the cipher + if (IV != null) + { + cipher.init(false, new ParametersWithIV(new KeyParameter(K1), IV)); + } + else + { + cipher.init(false, new KeyParameter(K1)); + } + + M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())]; + len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0); + len += cipher.doFinal(M, len); + } + + + // Convert the length of the encoding vector into a byte array. + byte[] P2 = param.getEncodingV(); + + // Verify the MAC. + int end = inOff + inLen; + byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end); + + byte[] T2 = new byte[T1.length]; + byte[] K2a; + if (hashK2) { + K2a = new byte[hash.getDigestSize()]; + hash.reset(); + hash.update(K2, 0, K2.length); + hash.doFinal(K2a, 0); + } else { + K2a = K2; + } + mac.init(new KeyParameter(K2a)); + mac.update(IV, 0, IV.length); + mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length); + + if (P2 != null) + { + mac.update(P2, 0, P2.length); + } + + if (V.length != 0 && P2 != null) { + byte[] L2 = new byte[4]; + Pack.intToBigEndian(P2.length * 8, L2, 0); + mac.update(L2, 0, L2.length); + } + + if (macData != null) { + mac.update(macData, 0, macData.length); + } + + mac.doFinal(T2, 0); + + if (!Arrays.constantTimeAreEqual(T1, T2)) + { + throw new InvalidCipherTextException("Invalid MAC."); + } + + + // Output the message. + return Arrays.copyOfRange(M, 0, len); + } + + public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { + return processBlock(in, inOff, inLen, null); + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen, + byte[] macData) + throws InvalidCipherTextException + { + if (forEncryption) + { + if (keyPairGenerator != null) + { + EphemeralKeyPair ephKeyPair = keyPairGenerator.generate(); + + this.privParam = ephKeyPair.getKeyPair().getPrivate(); + this.V = ephKeyPair.getEncodedPublicKey(); + } + } + else + { + if (keyParser != null) + { + ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen); + + try + { + this.pubParam = keyParser.readKey(bIn); + } + catch (IOException e) + { + throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); + } + + int encLength = (inLen - bIn.available()); + this.V = Arrays.copyOfRange(in, inOff, inOff + encLength); + } + } + + // Compute the common value and convert to byte array. + agree.init(privParam); + BigInteger z = agree.calculateAgreement(pubParam); + byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z); + + // Create input to KDF. + byte[] VZ; +// if (V.length != 0) +// { +// VZ = new byte[V.length + Z.length]; +// System.arraycopy(V, 0, VZ, 0, V.length); +// System.arraycopy(Z, 0, VZ, V.length, Z.length); +// } +// else + { + VZ = Z; + } + + // Initialise the KDF. + DerivationParameters kdfParam; + if (kdf instanceof MGF1BytesGeneratorExt) { + kdfParam = new MGFParameters(VZ); + } else { + kdfParam = new KDFParameters(VZ, param.getDerivationV()); + } + kdf.init(kdfParam); + + return forEncryption + ? encryptBlock(in, inOff, inLen, macData) + : decryptBlock(in, inOff, inLen, macData); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/ECIESPublicKeyEncoder.java b/crypto/src/main/java/org/platon/crypto/ECIESPublicKeyEncoder.java new file mode 100644 index 0000000..b3360e7 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/ECIESPublicKeyEncoder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto; + +import org.bouncycastle.crypto.KeyEncoder; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; + +/** + * Created by Anton Nashatyrev on 01.10.2015. + */ +public class ECIESPublicKeyEncoder implements KeyEncoder { + @Override + public byte[] getEncoded(AsymmetricKeyParameter asymmetricKeyParameter) { + return ((ECPublicKeyParameters) asymmetricKeyParameter).getQ().getEncoded(false); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/ECKey.java b/crypto/src/main/java/org/platon/crypto/ECKey.java new file mode 100644 index 0000000..b78471c --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/ECKey.java @@ -0,0 +1,875 @@ +package org.platon.crypto; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.platon.common.utils.ByteUtil; +import org.platon.crypto.config.Constants; +import org.platon.crypto.jce.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.encoders.Hex; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.platon.common.utils.BIUtil.isLessThan; + +public class ECKey implements Serializable { + private static final Logger logger = LoggerFactory.getLogger(ECKey.class); + + /** + * The parameters of the secp256k1 curve + */ + public static final ECDomainParameters CURVE; + public static final ECParameterSpec CURVE_SPEC; + + /** + * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. + */ + public static final BigInteger HALF_CURVE_ORDER; + + private static final SecureRandom secureRandom; + private static final long serialVersionUID = -728224901792295832L; + + static { + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); + HALF_CURVE_ORDER = params.getN().shiftRight(1); + secureRandom = new SecureRandom(); + } + + private final PrivateKey privKey; + protected final ECPoint pub; + + // the Java Cryptographic Architecture provider to use for Signature + private final Provider provider; + + // Transient because it's calculated on demand. + transient private byte[] pubKeyHash; + + /** + * Generates an entirely new keypair. + *

+ * BouncyCastle will be used as the Java Security Provider + */ + public ECKey() { + this(secureRandom); + } + + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return CURVE.getCurve().createPoint(xCoord, yCoord); + } + + /** + * Generate a new keypair using the given Java Security Provider. + * All private key operations will use the provider. + */ + public ECKey(Provider provider, SecureRandom secureRandom) { + this.provider = provider; + + final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom); + final KeyPair keyPair = keyPairGen.generateKeyPair(); + + this.privKey = keyPair.getPrivate(); + + final PublicKey pubKey = keyPair.getPublic(); + if (pubKey instanceof BCECPublicKey) { + pub = ((BCECPublicKey) pubKey).getQ(); + } else if (pubKey instanceof ECPublicKey) { + pub = extractPublicKey((ECPublicKey) pubKey); + } else { + throw new AssertionError( + "Expected Provider " + provider.getName() + + " to produce a subtype of ECPublicKey, found " + pubKey.getClass()); + } + } + + /** + * Generates an entirely new keypair with the given {@link SecureRandom} object. + *

+ * BouncyCastle will be used as the Java Security Provider + * + * @param secureRandom - + */ + public ECKey(SecureRandom secureRandom) { +// this(NewBouncyCastleProvider.getInstance(), secureRandom); + this(new BouncyCastleProvider(), secureRandom); + } + + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm().equals("EC"); + } + + /** + * Pair a private key with a public EC point. + * All private key operations will use the provider. + */ + public ECKey(Provider provider, PrivateKey privKey, ECPoint pub) { + this.provider = provider; + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with class " + + privKey.getClass().toString() + + " and algorithm " + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(NewBouncyCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, CURVE_SPEC)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /** + * Pair a private key integer with a public EC point + * BouncyCastle will be used as the Java Security Provider + */ + public ECKey(BigInteger priv, ECPoint pub) { + this( + NewBouncyCastleProvider.getInstance(), + privateKeyFromBigInteger(priv), + pub + ); + } + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already compressed. + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already compressed. + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKey + * @return + */ + public static ECKey fromPrivate(BigInteger privKey) { + return new ECKey(privKey, CURVE.getG().multiply(privKey)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static ECKey fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of multiplying the + * generator point by the private key. This is used to speed things up when you know you have the right values + * already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub) { + return new ECKey(priv, pub); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of multiplying the + * generator point by the private key. This is used to speed things up when you know you have the right values + * already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) { + CheckUtil.check(priv != null, "Private key must not be null"); + CheckUtil.check(pub != null, "Public key must not be null"); + return new ECKey(new BigInteger(1, priv), CURVE.getCurve().decodePoint(pub)); + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey compress() { + if (pub.isCompressed()) + return this; + else + return new ECKey(this.provider, this.privKey, compressPoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, use + * new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { + ECPoint point = CURVE.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute an address from a public point. + * + * @param pubPoint a public point + * @return 20-byte address + */ + public static byte[] computeAddress(ECPoint pubPoint) { + return WalletUtil.computeAddress(pubPoint.getEncoded(false)); + } + + /** + * Gets the address form of the public key. + * + * @return 20-byte address + */ + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = computeAddress(this.pub); + } + return pubKeyHash; + } + + /** + * Compute the encoded X, Y coordinates of a public point. + *

+ * This is the encoded public key without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by performing EC + * point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws java.lang.IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new MissingPrivateKeyException(); + } + } + + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. + * Unless you absolutely need the private key it is better for jce reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) privKey).getD().toByteArray())); + } + return b.toString(); + } + + /** + * Groups the two components that make up a signature, and provides a way to encode to Base64 form, which is + * how ECDSA signatures are represented when embedded in other data structures in the Ethereum protocol. The raw + * components can be useful for doing further EC maths on them. + */ + public static class ECDSASignature { + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. + * + * @param r - + * @param s - + */ + public ECDSASignature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. + * + * @param r + * @param s + * @param v + */ + public ECDSASignature(BigInteger r, BigInteger s, byte v) { + this.r = r; + this.s = s; + this.v = v; + } + + /** + * t + * + * @param r + * @param s + * @return - + */ + private static ECDSASignature fromComponents(byte[] r, byte[] s) { + return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte v) { + ECDSASignature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + public static boolean validateComponents(BigInteger r, BigInteger s, byte v) { + + if (v != 27 && v != 28) return false; + + if (isLessThan(r, BigInteger.ONE)) return false; + if (isLessThan(s, BigInteger.ONE)) return false; + + if (!isLessThan(r, Constants.SECP256K1N)) return false; + if (!isLessThan(s, Constants.SECP256K1N)) return false; + + return true; + } + + public static ECDSASignature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) + throw new RuntimeException("Reached past end of ASN.1 stream."); + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be + // Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html + return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) + try { + decoder.close(); + } catch (IOException x) { + } + } + } + + /** + * Will automatically adjust the S component to be less than or equal to half the curve order, if necessary. + * This is required because for every signature (r,s) the signature (r, -s (mod N)) is a valid signature of + * the same message. However, we dislike the ability to modify the bits of a Ethereum transaction after it's + * been signed, as that violates various assumed invariants. Thus in future only one of those forms will be + * considered legal and the other will be banned. + * + * @return - + */ + public ECDSASignature toCanonicalised() { + if (s.compareTo(HALF_CURVE_ORDER) > 0) { + // The order of the curve is the number of valid points that exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, which is canonical. + return new ECDSASignature(r, CURVE.getN().subtract(s)); + } else { + return this; + } + } + + public byte[] toByteArray() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S + sigData[0] = v; + System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(ByteUtil.bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return sigData; + } + + public static ECDSASignature toSignature(byte[] signature) { + if (signature.length != 65) { + throw new RuntimeException("signature bytes must be 65"); + } + byte[] r, s; + r = Arrays.copyOfRange(signature, 1, 33); + s = Arrays.copyOfRange(signature, 33, 65); + return new ECDSASignature(ByteUtil.bytesToBigInteger(r), ByteUtil.bytesToBigInteger(s), signature[0]); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ECDSASignature signature = (ECDSASignature) o; + + if (!r.equals(signature.r)) return false; + if (!s.equals(signature.s)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers + * and put them in ECDSASignature + * + * @param input to sign + * @return ECDSASignature signature that contains the R and S components + */ + public ECDSASignature doSign(byte[] input) { + if (input.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to ECDSA signature, not " + input.length); + } + // No decryption of private key required. + if (privKey == null) + throw new MissingPrivateKeyException(); + if (privKey instanceof BCECPrivateKey) { + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters(((BCECPrivateKey) privKey).getD(), CURVE); + signer.init(true, privKeyParams); + BigInteger[] components = signer.generateSignature(input); + return new ECDSASignature(components[0], components[1]).toCanonicalised(); + } else { + try { + final Signature ecSig = ECSignatureFactory.getRawInstance(provider); + ecSig.initSign(privKey); + ecSig.update(input); + final byte[] derSignature = ecSig.sign(); + return ECDSASignature.decodeFromDER(derSignature).toCanonicalised(); + } catch (SignatureException | InvalidKeyException ex) { + throw new RuntimeException("ECKey signing error", ex); + } + } + } + + + /** + * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public ECDSASignature sign(byte[] messageHash) { + ECDSASignature sig = doSign(messageHash); + // Now we have to work backwards to figure out the recId needed to recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) + throw new RuntimeException("Could not construct a recoverable key. This should never happen."); + sig.v = (byte) (recId + 27); + return sig; + } + + public BigInteger keyAgreement(ECPoint otherParty) { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) privKey).getD(), CURVE)); + return agreement.calculateAgreement(new ECPublicKeyParameters(otherParty, CURVE)); + } else { + try { + final KeyAgreement agreement = ECKeyAgreement.getInstance(this.provider); + agreement.init(this.privKey); + agreement.doPhase( + ECKeyFactory.getInstance(this.provider) + .generatePublic(new ECPublicKeySpec(otherParty, CURVE_SPEC)), + /* lastPhase */ true); + return new BigInteger(1, agreement.generateSecret()); + } catch (IllegalStateException | InvalidKeyException | InvalidKeySpecException ex) { + throw new RuntimeException("ECDH key agreement failure", ex); + } + } + } + + /** + * Verifies the given ECDSA signature against + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return + */ + public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + try { + return signer.verifySignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ECDSA signature against + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return + */ + public boolean verify(byte[] data, ECDSASignature signature) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(getPubKey()), CURVE); + signer.init(false, params); + try { + return signer.verifySignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, ECDSASignature.toSignature(signature), pub); + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return ECKey.verify(data, signature, getPubKey()); + } + + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + if (pubkey.length != 65) + return false; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + if (pubkey.length != 33) + return false; + } else + return false; + return true; + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the correct one. Because + * the key recovery operation yields multiple potential keys, the correct key must either be stored alongside the + * signature, or you must be willing to try each recId in turn until you find one that outputs the key you are + * expecting.

+ * + *

If this method returns null it means recovery was not possible and recId should be iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the + * output is null OR a key that is not the one you expect, you try again with the next recId.

+ * + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 65-byte encoded public key + */ + public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignature sig, byte[] messageHash) { + CheckUtil.check(recId >= 0, "recId must be positive"); + CheckUtil.check(sig.r.signum() >= 0, "r must be positive"); + CheckUtil.check(sig.s.signum() >= 0, "s must be positive"); + CheckUtil.check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + BigInteger n = CURVE.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) + return null; + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public only + * + * @return - + */ + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return ByteUtil.bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + /** + * + * @param ecPoint + * @return + * @throws InvalidKeySpecException + */ + public static ECPublicKey getECPublicKey(final ECPoint ecPoint) throws InvalidKeySpecException { + return (ECPublicKey) ECKeyFactory.getInstance(NewBouncyCastleProvider.getInstance()) + .generatePublic(new ECPublicKeySpec(ecPoint, CURVE_SPEC)); + } + + /** + * + * @param publicKeyBytes + * @return + * @throws InvalidKeySpecException + */ + public static ECPublicKey getECPublicKeyFromBytes(byte[] publicKeyBytes) throws InvalidKeySpecException { + return (ECPublicKey) ECKeyFactory.getInstance(NewBouncyCastleProvider.getInstance()) + .generatePublic(new ECPublicKeySpec(CURVE.getCurve().decodePoint(publicKeyBytes), CURVE_SPEC)); + } + +// /** +// * ECIES encrypt +// * +// * @param message +// * @return +// */ +// public byte[] encrypt(byte[] message) { +// try { +// Cipher encrypter = Cipher.getInstance(ECKeyFactory.ENCRYPT_ALGORITHM, provider); +// encrypter.init(Cipher.ENCRYPT_MODE, ECKey.getECPublicKey(pub)); +// return encrypter.doFinal(message); +// } catch (Exception e) { +// logger.error("encrypt error", e); +// return null; +// } +// } +// +// /** +// * ECIES decrypt +// * +// * @param encryptMessage +// * @return +// */ +// public byte[] decrypt(byte[] encryptMessage) { +// try { +// Cipher decrypter = Cipher.getInstance(ECKeyFactory.ENCRYPT_ALGORITHM, provider); +// decrypter.init(Cipher.DECRYPT_MODE, privKey); +// return decrypter.doFinal(encryptMessage); +// } catch (Exception e) { +// logger.error("decrypt error", e); +// return null; +// } +// } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof ECKey)) return false; + + ECKey ecKey = (ECKey) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) return false; + if (pub != null && !pub.equals(ecKey.pub)) return false; + + return true; + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + @SuppressWarnings("serial") + public static class MissingPrivateKeyException extends RuntimeException { + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/platon/crypto/HashUtil.java b/crypto/src/main/java/org/platon/crypto/HashUtil.java new file mode 100644 index 0000000..a2ce8e8 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/HashUtil.java @@ -0,0 +1,165 @@ +package org.platon.crypto; + +import org.platon.common.utils.ByteUtil; +import org.platon.crypto.jce.NewBouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.RIPEMD160Digest; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Random; + +public class HashUtil { + + private static final Logger logger = LoggerFactory.getLogger(HashUtil.class); + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private static final Provider CRYPTO_PROVIDER; + + private static final String HASH_256_ALGORITHM_NAME; + + public static final byte[] EMPTY_DATA_HASH; + + public static final byte[] EMPTY_HASH; + + static { + Security.addProvider(NewBouncyCastleProvider.getInstance()); + CRYPTO_PROVIDER = Security.getProvider("BC"); + HASH_256_ALGORITHM_NAME = "ETH-KECCAK-256"; + EMPTY_HASH = sha3(ByteUtil.EMPTY_BYTE_ARRAY); + EMPTY_DATA_HASH = sha3(EMPTY_BYTE_ARRAY); + } + /** + * sha3 + * + * @param data data + * @return hash of the data + */ + public static byte[] sha3(byte[] data) { + if(null == data) { + return EMPTY_HASH; + } + return sha3(data,0,data.length); + } + + /** + * sha3 twice + * @param data data + * @return hash of the data + */ + public static byte[] sha3Twice(byte[] data) { + return sha3Twice(data,0,data.length); + } + + /** + * sha3 + * + * @param data1 data1 + * @param data2 data2 + * @return hash of the data + */ + public static byte[] sha3(byte[] data1, byte[] data2) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, CRYPTO_PROVIDER); + digest.update(data1, 0, data1.length); + digest.update(data2, 0, data2.length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error("Can not find algorithm", e); + throw new RuntimeException(e); + } + } + + /** + * sha3 + * + * @param data data + * @param start start of hashing chunk + * @param length length of hashing chunk + * @return keccak hash of the chunk + */ + public static byte[] sha3(byte[] data, int start, int length) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, CRYPTO_PROVIDER); + digest.update(data, start, length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error("Can not find algorithm", e); + throw new RuntimeException(e); + } + } + + /** + * sha3 twice + * + * @param data data + * @param start start of hashing chunk + * @param length length of hashing chunk + * @return keccak hash of the chunk + */ + public static byte[] sha3Twice(byte[] data, int start, int length) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, CRYPTO_PROVIDER); + digest.update(data, start, length); + return digest.digest(digest.digest()); + } catch (NoSuchAlgorithmException e) { + logger.error("Can not find algorithm", e); + throw new RuntimeException(e); + } + } + + /** + * ripemd160 + * + * @param data data + * @return hash of data + */ + public static byte[] ripemd160(byte[] data) { + Digest digest = new RIPEMD160Digest(); + if (data != null) { + byte[] resBuf = new byte[digest.getDigestSize()]; + digest.update(data, 0, data.length); + digest.doFinal(resBuf, 0); + return resBuf; + } + throw new NullPointerException("Can't hash a NULL value"); + } + + /** + * RIPEMD160(sha3(input)) + * + * @param data data + * @return hash of data + */ + public static byte[] sha3Hash160(byte[] data) { + byte[] sha3 = sha3(data); + Digest digest = new RIPEMD160Digest(); + if (data != null) { + byte[] resBuf = new byte[digest.getDigestSize()]; + digest.update(sha3, 0, sha3.length); + digest.doFinal(resBuf, 0); + return resBuf; + } + throw new NullPointerException("Can't hash a NULL value"); + } + + /** + * random 32 byte hash + * + * @return + */ + public static byte[] randomHash32() { + byte[] randomHash = new byte[32]; + Random random = new Random(); + random.nextBytes(randomHash); + return randomHash; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/MGF1BytesGeneratorExt.java b/crypto/src/main/java/org/platon/crypto/MGF1BytesGeneratorExt.java new file mode 100644 index 0000000..f122273 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/MGF1BytesGeneratorExt.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto; + +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.DerivationFunction; +import org.bouncycastle.crypto.DerivationParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.params.MGFParameters; + +/** + * This class is borrowed from spongycastle project + * The only change made is addition of 'counterStart' parameter to + * conform to Crypto++ capabilities + */ +public class MGF1BytesGeneratorExt implements DerivationFunction { + private Digest digest; + private byte[] seed; + private int hLen; + private int counterStart; + + public MGF1BytesGeneratorExt(Digest digest, int counterStart) { + this.digest = digest; + this.hLen = digest.getDigestSize(); + this.counterStart = counterStart; + } + + public void init(DerivationParameters param) { + if(!(param instanceof MGFParameters)) { + throw new IllegalArgumentException("MGF parameters required for MGF1Generator"); + } else { + MGFParameters p = (MGFParameters)param; + this.seed = p.getSeed(); + } + } + + public Digest getDigest() { + return this.digest; + } + + private void ItoOSP(int i, byte[] sp) { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException, IllegalArgumentException { + if(out.length - len < outOff) { + throw new DataLengthException("output buffer too small"); + } else { + byte[] hashBuf = new byte[this.hLen]; + byte[] C = new byte[4]; + int counter = 0; + int hashCounter = counterStart; + this.digest.reset(); + if(len > this.hLen) { + do { + this.ItoOSP(hashCounter++, C); + this.digest.update(this.seed, 0, this.seed.length); + this.digest.update(C, 0, C.length); + this.digest.doFinal(hashBuf, 0); + System.arraycopy(hashBuf, 0, out, outOff + counter * this.hLen, this.hLen); + ++counter; + } while(counter < len / this.hLen); + } + + if(counter * this.hLen < len) { + this.ItoOSP(hashCounter, C); + this.digest.update(this.seed, 0, this.seed.length); + this.digest.update(C, 0, C.length); + this.digest.doFinal(hashBuf, 0); + System.arraycopy(hashBuf, 0, out, outOff + counter * this.hLen, len - counter * this.hLen); + } + + return len; + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/WalletUtil.java b/crypto/src/main/java/org/platon/crypto/WalletUtil.java new file mode 100644 index 0000000..63a4282 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/WalletUtil.java @@ -0,0 +1,284 @@ +package org.platon.crypto; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang.StringUtils; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; +import org.platon.crypto.domain.WalletJson; +import org.platon.crypto.jce.ECKeyFactory; +import org.platon.crypto.jce.NewBouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import java.io.*; +import java.security.SignatureException; +import java.util.Arrays; + +import static org.platon.crypto.ECKey.CURVE; + +public class WalletUtil { + + private static final Logger logger = LoggerFactory.getLogger(WalletUtil.class); + + /** + * generateECKey + * @return + */ + public static ECKey generateECKey(){ + return new ECKey(); + } + + /** + * Temporary generateWallet + * @param walletPath + * @return + */ + public static ECKey generateWallet(String walletPath){ + return generateWallet(walletPath,null); + } + + /** + * Temporary generateWallet + * @param walletPath + * @param walletName if walletName is null,walletName is address + * @return + * TODO : Wallet must rewrite + */ + public static ECKey generateWallet(String walletPath,String walletName){ + try { + //generate eckey + ECKey eckey = generateECKey(); + //generate walletJsonStr + WalletJson walletJson = new WalletJson(); + walletJson.setAddress(Hex.toHexString(eckey.getAddress())); + walletJson.setPrivateKey(Hex.toHexString(eckey.getPrivKeyBytes())); + walletJson.setPublicKey(Hex.toHexString(eckey.getPubKey())); + String walletJsonStr = JSON.toJSONString(walletJson); + //generate walletFile + String walletUrl; + if(StringUtils.isNotBlank(walletName)){ + walletUrl = walletPath+File.separator+walletName; + }else{ + walletUrl = walletPath+File.separator+walletJson.getAddress(); + } + File file = new File(walletUrl); + if (!file.exists()) { + file.createNewFile(); + } + //write walletFile + BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); + bufferedWriter.write(walletJsonStr); + bufferedWriter.flush(); + bufferedWriter.close(); + return eckey; + } catch (Exception e) { + logger.error("generate Wallet error:",e); + return null; + } + } + + /** + * Temporary loadWallet + * @param walletUrl + * @return + * TODO : Wallet must rewrite + */ + public static ECKey loadWallet(String walletUrl){ + //get walletFile + File file = new File(walletUrl); + String walletJsonStr; + try { + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + StringBuffer stringBuffer = new StringBuffer(); + String line = null; + while ((line = bufferedReader.readLine()) != null){ + stringBuffer.append(line); + } + bufferedReader.close(); + //analyze walletJson + walletJsonStr = stringBuffer.toString(); + WalletJson walletJson = JSON.parseObject(walletJsonStr,WalletJson.class); + byte[] priv = Hex.decode(walletJson.getPrivateKey()); + byte[] pub = Hex.decode(walletJson.getPublicKey()); + //generate ECKey + return ECKey.fromPrivateAndPrecalculatedPublic(priv,pub); + } catch (Exception e) { + logger.error("loadWallet error:",e); + return null; + } + + } + + /** + * signature message + * @param messageHash + * @param ecKey + * @return base64 sign byte[] + */ + public static byte[] sign(byte[] messageHash,ECKey ecKey){ + return Base64.encode(ecKey.sign(messageHash).toByteArray()); + } + + public static byte[] sign(byte[] messageHash,byte chainId, ECKey ecKey){ + ECKey.ECDSASignature signature = ecKey.sign(messageHash); + byte tv = signature.v; + byte v = (byte) (tv + (chainId << 1) + 8); + signature.v = v; + return Base64.encode(signature.toByteArray()); + } + + /** + * signature message + * @param messageHash + * @param ecKey + * @return ECDSASignature + */ + public static ECKey.ECDSASignature signature(byte[] messageHash, ECKey ecKey){ + return ecKey.sign(messageHash); + } + + /** + * signature verify + * @param messageHash + * @param signature + * @param address + * @return -true is verify passed,false is verify failed + */ + public static boolean verify(byte[] messageHash, byte[] signature, byte[] address) { + try { + byte[] signAddress = WalletUtil.signatureToAddress(messageHash,signature); + return Arrays.equals(signAddress,address); + } catch (SignatureException e) { + logger.error("verify error:",e); + return false; + } + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature you can use new String(signature) take a signatureBase64 + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { + return computeAddress(signatureToPubKeyBytes(messageHash, signatureBase64.getBytes())); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signature Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, byte[] signature) throws SignatureException { + return computeAddress(signatureToPubKeyBytes(messageHash, signature)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signature Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, ECKey.ECDSASignature signature) throws SignatureException { + return computeAddress(signatureToPubKeyBytes(messageHash, signature)); + } + + /** + * signatureToPubKeyBytes + * @param messageHash + * @param signatureEncoded + * @return + * @throws SignatureException + */ + public static byte[] signatureToPubKeyBytes(byte[] messageHash, byte[] signatureEncoded) throws SignatureException { + try { + signatureEncoded = Base64.decode(signatureEncoded); + } catch (RuntimeException e) { + throw new SignatureException("Could not decode base64", e); + } + if (signatureEncoded.length < 65) + throw new SignatureException("Signature truncated, expected 65 bytes and got " + signatureEncoded.length); + + return signatureToPubKeyBytes( + messageHash, + ECKey.ECDSASignature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + /** + * get publicKey from signature + * @param messageHash + * @param sig + * @return + * @throws SignatureException + */ + private static byte[] signatureToPubKeyBytes(byte[] messageHash, ECKey.ECDSASignature sig) throws SignatureException { + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key with odd y, + // 0x1D = second key with even y, 0x1E = second key with odd y + if (header < 27 || header > 34) + throw new SignatureException("Header byte out of range: " + header); + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, messageHash); + if (key == null) + throw new SignatureException("Could not recover public key from signature"); + return key; + } + + /** + * Compute an address from an encoded public key. + * @param pubBytes + * @return 20-byte address + */ + public static byte[] computeAddress(byte[] pubBytes) { + return HashUtil.sha3Hash160(pubBytes); + } + + /** + * ECDSASignature sign cover + * @param signature + * @return + */ + public static byte[] signToByteArray(ECKey.ECDSASignature signature){ + return Base64.encode(signature.toByteArray()); + } + + /** + * byte[] sign cover ECDSASignature + * @param signatureByteArray + * @return + */ + public static ECKey.ECDSASignature signToECDSASignature(byte[] signatureByteArray){ + return ECKey.ECDSASignature.toSignature(Base64.decode(signatureByteArray)); + } + + public static byte[] encrypt(byte[] message,byte[] publicKey){ + return ECIESCoder.encrypt(ECKey.CURVE.getCurve().decodePoint(publicKey), message); + } + + public static byte[] encrypt(byte[] message,ECKey ecKey){ + return ECIESCoder.encrypt(ecKey.pub, message); + } + + public static byte[] decrypt(byte[] encryptMessage,ECKey ecKey){ + try{ + return ECIESCoder.decrypt(ecKey.getPrivKey(),encryptMessage); + }catch (Exception e){ + logger.error("decrypt error",e); + return null; + } + } + + + +} diff --git a/crypto/src/main/java/org/platon/crypto/config/Constants.java b/crypto/src/main/java/org/platon/crypto/config/Constants.java new file mode 100644 index 0000000..82af79f --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/config/Constants.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.config; + +import java.math.BigInteger; + +/** + * Describes different constants specific for a blockchain + * + * Created by Anton Nashatyrev on 25.02.2016. + */ +public class Constants { + public static final BigInteger SECP256K1N = new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); + + public static final int CRYPTO_VERSION = 1; +} diff --git a/crypto/src/main/java/org/platon/crypto/domain/WalletJson.java b/crypto/src/main/java/org/platon/crypto/domain/WalletJson.java new file mode 100644 index 0000000..db2eb23 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/domain/WalletJson.java @@ -0,0 +1,38 @@ +package org.platon.crypto.domain; + +/** + * Temporary WalletJson Model + * TODO : Wallet must rewrite + */ +public class WalletJson { + + private String address; + + private String publicKey; + + private String privateKey; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/hash/Digest.java b/crypto/src/main/java/org/platon/crypto/hash/Digest.java new file mode 100644 index 0000000..190217c --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/hash/Digest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +// $Id: Digest.java 232 2010-06-17 14:19:24Z tp $ + +package org.platon.crypto.hash; + +/** + *

This interface documents the API for a hash function. This + * interface somewhat mimics the standard {@code + * java.security.MessageDigest} class. We do not extend that class in + * order to provide compatibility with reduced Java implementations such + * as J2ME. Implementing a {@code java.security.Provider} compatible + * with Sun's JCA ought to be easy.

+ * + *

A {@code Digest} object maintains a running state for a hash + * function computation. Data is inserted with {@code update()} calls; + * the result is obtained from a {@code digest()} method (where some + * final data can be inserted as well). When a digest output has been + * produced, the objet is automatically resetted, and can be used + * immediately for another digest operation. The state of a computation + * can be cloned with the {@link #copy} method; this can be used to get + * a partial hash result without interrupting the complete + * computation.

+ * + *

{@code Digest} objects are stateful and hence not thread-safe; + * however, distinct {@code Digest} objects can be accessed concurrently + * without any problem.

+ * + *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010  Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ * 
+ * + * @version $Revision: 232 $ + * @author Thomas Pornin <thomas.pornin@cryptolog.com> + */ + +public interface Digest{ + + /** + * Insert one more input data byte. + * + * @param in the input byte + */ + void update(byte in); + + /** + * Insert some more bytes. + * + * @param inbuf the data bytes + */ + void update(byte[] inbuf); + + /** + * Insert some more bytes. + * + * @param inbuf the data buffer + * @param off the data offset in {@code inbuf} + * @param len the data length (in bytes) + */ + void update(byte[] inbuf, int off, int len); + + /** + * Finalize the current hash computation and return the hash value + * in a newly-allocated array. The object is resetted. + * + * @return the hash output + */ + byte[] digest(); + + /** + * Input some bytes, then finalize the current hash computation + * and return the hash value in a newly-allocated array. The object + * is resetted. + * + * @param inbuf the input data + * @return the hash output + */ + byte[] digest(byte[] inbuf); + + /** + * Finalize the current hash computation and store the hash value + * in the provided output buffer. The {@code len} parameter + * contains the maximum number of bytes that should be written; + * no more bytes than the natural hash function output length will + * be produced. If {@code len} is smaller than the natural + * hash output length, the hash output is truncated to its first + * {@code len} bytes. The object is resetted. + * + * @param outbuf the output buffer + * @param off the output offset within {@code outbuf} + * @param len the requested hash output length (in bytes) + * @return the number of bytes actually written in {@code outbuf} + */ + int digest(byte[] outbuf, int off, int len); + + /** + * Get the natural hash function output length (in bytes). + * + * @return the digest output length (in bytes) + */ + int getDigestLength(); + + /** + * Reset the object: this makes it suitable for a new hash + * computation. The current computation, if any, is discarded. + */ + void reset(); + + /** + * Clone the current state. The returned object evolves independantly + * of this object. + * + * @return the clone + */ + Digest copy(); + + /** + *

Return the "block length" for the hash function. This + * value is naturally defined for iterated hash functions + * (Merkle-Damgard). It is used in HMAC (that's what the + * HMAC specification + * names the "{@code B}" parameter).

+ * + *

If the function is "block-less" then this function may + * return {@code -n} where {@code n} is an integer such that the + * block length for HMAC ("{@code B}") will be inferred from the + * key length, by selecting the smallest multiple of {@code n} + * which is no smaller than the key length. For instance, for + * the Fugue-xxx hash functions, this function returns -4: the + * virtual block length B is the HMAC key length, rounded up to + * the next multiple of 4.

+ * + * @return the internal block length (in bytes), or {@code -n} + */ + int getBlockLength(); + + /** + *

Get the display name for this function (e.g. {@code "SHA-1"} + * for SHA-1).

+ * + * @see Object + */ + String toString(); +} diff --git a/crypto/src/main/java/org/platon/crypto/hash/DigestEngine.java b/crypto/src/main/java/org/platon/crypto/hash/DigestEngine.java new file mode 100644 index 0000000..c6eafe7 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/hash/DigestEngine.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +// $Id: DigestEngine.java 229 2010-06-16 20:22:27Z tp $ + +package org.platon.crypto.hash; + +import java.security.MessageDigest; + +/** + *

This class is a template which can be used to implement hash + * functions. It takes care of some of the API, and also provides an + * internal data buffer whose length is equal to the hash function + * internal block length.

+ * + *

Classes which use this template MUST provide a working {@link + * #getBlockLength} method even before initialization (alternatively, + * they may define a custom {@link #getInternalBlockLength} which does + * not call {@link #getBlockLength}. The {@link #getDigestLength} should + * also be operational from the beginning, but it is acceptable that it + * returns 0 while the {@link #doInit} method has not been called + * yet.

+ * + *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010  Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ * 
+ * + * @version $Revision: 229 $ + * @author Thomas Pornin <thomas.pornin@cryptolog.com> + */ + +public abstract class DigestEngine extends MessageDigest implements Digest { + + /** + * Reset the hash algorithm state. + */ + protected abstract void engineReset(); + + /** + * Process one block of data. + * + * @param data the data block + */ + protected abstract void processBlock(byte[] data); + + /** + * Perform the final padding and store the result in the + * provided buffer. This method shall call {@link #flush} + * and then {@link #update} with the appropriate padding + * data in order to get the full input data. + * + * @param buf the output buffer + * @param off the output offset + */ + protected abstract void doPadding(byte[] buf, int off); + + /** + * This function is called at object creation time; the + * implementation should use it to perform initialization tasks. + * After this method is called, the implementation should be ready + * to process data or meaningfully honour calls such as + * {@link #engineGetDigestLength} + */ + protected abstract void doInit(); + + private int digestLen, blockLen, inputLen; + private byte[] inputBuf, outputBuf; + private long blockCount; + + /** + * Instantiate the engine. + */ + public DigestEngine(String alg) + { + super(alg); + doInit(); + digestLen = engineGetDigestLength(); + blockLen = getInternalBlockLength(); + inputBuf = new byte[blockLen]; + outputBuf = new byte[digestLen]; + inputLen = 0; + blockCount = 0; + } + + private void adjustDigestLen() + { + if (digestLen == 0) { + digestLen = engineGetDigestLength(); + outputBuf = new byte[digestLen]; + } + } + + public byte[] digest() + { + adjustDigestLen(); + byte[] result = new byte[digestLen]; + digest(result, 0, digestLen); + return result; + } + + public byte[] digest(byte[] input) + { + update(input, 0, input.length); + return digest(); + } + + public int digest(byte[] buf, int offset, int len) + { + adjustDigestLen(); + if (len >= digestLen) { + doPadding(buf, offset); + reset(); + return digestLen; + } else { + doPadding(outputBuf, 0); + System.arraycopy(outputBuf, 0, buf, offset, len); + reset(); + return len; + } + } + + public void reset() + { + engineReset(); + inputLen = 0; + blockCount = 0; + } + + public void update(byte input) + { + inputBuf[inputLen ++] = (byte)input; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + + public void update(byte[] input) + { + update(input, 0, input.length); + } + + public void update(byte[] input, int offset, int len) + { + while (len > 0) { + int copyLen = blockLen - inputLen; + if (copyLen > len) + copyLen = len; + System.arraycopy(input, offset, inputBuf, inputLen, + copyLen); + offset += copyLen; + inputLen += copyLen; + len -= copyLen; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + } + + /** + * Get the internal block length. This is the length (in + * bytes) of the array which will be passed as parameter to + * {@link #processBlock}. The default implementation of this + * method calls {@link #getBlockLength} and returns the same + * value. Overriding this method is useful when the advertised + * block length (which is used, for instance, by HMAC) is + * suboptimal with regards to internal buffering needs. + * + * @return the internal block length (in bytes) + */ + protected int getInternalBlockLength() + { + return getBlockLength(); + } + + /** + * Flush internal buffers, so that less than a block of data + * may at most be upheld. + * + * @return the number of bytes still unprocessed after the flush + */ + protected final int flush() + { + return inputLen; + } + + /** + * Get a reference to an internal buffer with the same size + * than a block. The contents of that buffer are defined only + * immediately after a call to {@link #flush()}: if + * {@link #flush()} return the value {@code n}, then the + * first {@code n} bytes of the array returned by this method + * are the {@code n} bytes of input data which are still + * unprocessed. The values of the remaining bytes are + * undefined and may be altered at will. + * + * @return a block-sized internal buffer + */ + protected final byte[] getBlockBuffer() + { + return inputBuf; + } + + /** + * Get the "block count": this is the number of times the + * {@link #processBlock} method has been invoked for the + * current hash operation. That counter is incremented + * after the call to {@link #processBlock}. + * + * @return the block count + */ + protected long getBlockCount() + { + return blockCount; + } + + /** + * This function copies the internal buffering state to some + * other instance of a class extending {@code DigestEngine}. + * It returns a reference to the copy. This method is intended + * to be called by the implementation of the {@link #copy} + * method. + * + * @param dest the copy + * @return the value {@code dest} + */ + protected Digest copyState(DigestEngine dest) + { + dest.inputLen = inputLen; + dest.blockCount = blockCount; + System.arraycopy(inputBuf, 0, dest.inputBuf, 0, + inputBuf.length); + adjustDigestLen(); + dest.adjustDigestLen(); + System.arraycopy(outputBuf, 0, dest.outputBuf, 0, + outputBuf.length); + return dest; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/hash/Keccak256.java b/crypto/src/main/java/org/platon/crypto/hash/Keccak256.java new file mode 100644 index 0000000..42a0667 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/hash/Keccak256.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +// $Id: Keccak256.java 189 2010-05-14 21:21:46Z tp $ + +package org.platon.crypto.hash; + +/** + *

This class implements the Keccak-256 digest algorithm under the + * + *

+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010  Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ * 
+ * + * @version $Revision: 189 $ + * @author Thomas Pornin <thomas.pornin@cryptolog.com> + */ + +public class Keccak256 extends KeccakCore { + + /** + * Create the engine. + */ + public Keccak256() + { + super("eth-keccak-256"); + } + + public Digest copy() + { + return copyState(new Keccak256()); + } + + public int engineGetDigestLength() + { + return 32; + } + + @Override + protected byte[] engineDigest() { + return null; + } + + @Override + protected void engineUpdate(byte arg0) { + } + + @Override + protected void engineUpdate(byte[] arg0, int arg1, int arg2) { + } +} diff --git a/crypto/src/main/java/org/platon/crypto/hash/Keccak512.java b/crypto/src/main/java/org/platon/crypto/hash/Keccak512.java new file mode 100644 index 0000000..19b799e --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/hash/Keccak512.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +// $Id: Keccak512.java 189 2010-05-14 21:21:46Z tp $ + +package org.platon.crypto.hash; + +/** + *

This class implements the Keccak-256 digest algorithm under the + * {@link Digest} API.

+ * + *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010  Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ * 
+ * + * @version $Revision: 189 $ + * @author Thomas Pornin <thomas.pornin@cryptolog.com> + */ + +public class Keccak512 extends KeccakCore { + + /** + * Create the engine. + */ + public Keccak512() + { + super("eth-keccak-512"); + } + + /** @see Digest */ + public Digest copy() + { + return copyState(new Keccak512()); + } + + /** @see Digest */ + public int engineGetDigestLength() + { + return 64; + } + + @Override + protected byte[] engineDigest() { + return null; + } + + @Override + protected void engineUpdate(byte input) { + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + } +} diff --git a/crypto/src/main/java/org/platon/crypto/hash/KeccakCore.java b/crypto/src/main/java/org/platon/crypto/hash/KeccakCore.java new file mode 100644 index 0000000..b4bf1e9 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/hash/KeccakCore.java @@ -0,0 +1,596 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +// $Id: KeccakCore.java 258 2011-07-15 22:16:50Z tp $ + +package org.platon.crypto.hash; + +/** + * This class implements the core operations for the Keccak digest + * algorithm. + * + *
+ * ==========================(LICENSE BEGIN)============================
+ *
+ * Copyright (c) 2007-2010  Projet RNRT SAPHIR
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ===========================(LICENSE END)=============================
+ * 
+ * + * @version $Revision: 258 $ + * @author Thomas Pornin <thomas.pornin@cryptolog.com> + */ + +abstract class KeccakCore extends DigestEngine{ + + KeccakCore(String alg) + { + super(alg); + } + + private long[] A; + private byte[] tmpOut; + + private static final long[] RC = { + 0x0000000000000001L, 0x0000000000008082L, + 0x800000000000808AL, 0x8000000080008000L, + 0x000000000000808BL, 0x0000000080000001L, + 0x8000000080008081L, 0x8000000000008009L, + 0x000000000000008AL, 0x0000000000000088L, + 0x0000000080008009L, 0x000000008000000AL, + 0x000000008000808BL, 0x800000000000008BL, + 0x8000000000008089L, 0x8000000000008003L, + 0x8000000000008002L, 0x8000000000000080L, + 0x000000000000800AL, 0x800000008000000AL, + 0x8000000080008081L, 0x8000000000008080L, + 0x0000000080000001L, 0x8000000080008008L + }; + + /** + * Encode the 64-bit word {@code val} into the array + * {@code buf} at offset {@code off}, in little-endian + * convention (least significant byte first). + * + * @param val the value to encode + * @param buf the destination buffer + * @param off the destination offset + */ + private static void encodeLELong(long val, byte[] buf, int off) + { + buf[off + 0] = (byte)val; + buf[off + 1] = (byte)(val >>> 8); + buf[off + 2] = (byte)(val >>> 16); + buf[off + 3] = (byte)(val >>> 24); + buf[off + 4] = (byte)(val >>> 32); + buf[off + 5] = (byte)(val >>> 40); + buf[off + 6] = (byte)(val >>> 48); + buf[off + 7] = (byte)(val >>> 56); + } + + /** + * Decode a 64-bit little-endian word from the array {@code buf} + * at offset {@code off}. + * + * @param buf the source buffer + * @param off the source offset + * @return the decoded value + */ + private static long decodeLELong(byte[] buf, int off) + { + return (buf[off + 0] & 0xFFL) + | ((buf[off + 1] & 0xFFL) << 8) + | ((buf[off + 2] & 0xFFL) << 16) + | ((buf[off + 3] & 0xFFL) << 24) + | ((buf[off + 4] & 0xFFL) << 32) + | ((buf[off + 5] & 0xFFL) << 40) + | ((buf[off + 6] & 0xFFL) << 48) + | ((buf[off + 7] & 0xFFL) << 56); + } + + protected void engineReset() + { + doReset(); + } + + protected void processBlock(byte[] data) + { + /* Input block */ + for (int i = 0; i < data.length; i += 8) + A[i >>> 3] ^= decodeLELong(data, i); + + long t0, t1, t2, t3, t4; + long tt0, tt1, tt2, tt3, tt4; + long t, kt; + long c0, c1, c2, c3, c4, bnn; + + /* + * Unrolling four rounds kills performance big time + * on Intel x86 Core2, in both 32-bit and 64-bit modes + * (less than 1 MB/s instead of 55 MB/s on x86-64). + * Unrolling two rounds appears to be fine. + */ + for (int j = 0; j < 24; j += 2) { + + tt0 = A[ 1] ^ A[ 6]; + tt1 = A[11] ^ A[16]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 4] ^ A[ 9]; + tt3 = A[14] ^ A[19]; + tt0 ^= A[24]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[ 2] ^ A[ 7]; + tt1 = A[12] ^ A[17]; + tt0 ^= A[22] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 5]; + tt3 = A[10] ^ A[15]; + tt0 ^= A[20]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[ 3] ^ A[ 8]; + tt1 = A[13] ^ A[18]; + tt0 ^= A[23] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 1] ^ A[ 6]; + tt3 = A[11] ^ A[16]; + tt0 ^= A[21]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[ 4] ^ A[ 9]; + tt1 = A[14] ^ A[19]; + tt0 ^= A[24] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 2] ^ A[ 7]; + tt3 = A[12] ^ A[17]; + tt0 ^= A[22]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 5]; + tt1 = A[10] ^ A[15]; + tt0 ^= A[20] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 3] ^ A[ 8]; + tt3 = A[13] ^ A[18]; + tt0 ^= A[23]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 5] = A[ 5] ^ t0; + A[10] = A[10] ^ t0; + A[15] = A[15] ^ t0; + A[20] = A[20] ^ t0; + A[ 1] = A[ 1] ^ t1; + A[ 6] = A[ 6] ^ t1; + A[11] = A[11] ^ t1; + A[16] = A[16] ^ t1; + A[21] = A[21] ^ t1; + A[ 2] = A[ 2] ^ t2; + A[ 7] = A[ 7] ^ t2; + A[12] = A[12] ^ t2; + A[17] = A[17] ^ t2; + A[22] = A[22] ^ t2; + A[ 3] = A[ 3] ^ t3; + A[ 8] = A[ 8] ^ t3; + A[13] = A[13] ^ t3; + A[18] = A[18] ^ t3; + A[23] = A[23] ^ t3; + A[ 4] = A[ 4] ^ t4; + A[ 9] = A[ 9] ^ t4; + A[14] = A[14] ^ t4; + A[19] = A[19] ^ t4; + A[24] = A[24] ^ t4; + A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36)); + A[10] = (A[10] << 3) | (A[10] >>> (64 - 3)); + A[15] = (A[15] << 41) | (A[15] >>> (64 - 41)); + A[20] = (A[20] << 18) | (A[20] >>> (64 - 18)); + A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1)); + A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44)); + A[11] = (A[11] << 10) | (A[11] >>> (64 - 10)); + A[16] = (A[16] << 45) | (A[16] >>> (64 - 45)); + A[21] = (A[21] << 2) | (A[21] >>> (64 - 2)); + A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62)); + A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6)); + A[12] = (A[12] << 43) | (A[12] >>> (64 - 43)); + A[17] = (A[17] << 15) | (A[17] >>> (64 - 15)); + A[22] = (A[22] << 61) | (A[22] >>> (64 - 61)); + A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28)); + A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55)); + A[13] = (A[13] << 25) | (A[13] >>> (64 - 25)); + A[18] = (A[18] << 21) | (A[18] >>> (64 - 21)); + A[23] = (A[23] << 56) | (A[23] >>> (64 - 56)); + A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27)); + A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20)); + A[14] = (A[14] << 39) | (A[14] >>> (64 - 39)); + A[19] = (A[19] << 8) | (A[19] >>> (64 - 8)); + A[24] = (A[24] << 14) | (A[24] >>> (64 - 14)); + bnn = ~A[12]; + kt = A[ 6] | A[12]; + c0 = A[ 0] ^ kt; + kt = bnn | A[18]; + c1 = A[ 6] ^ kt; + kt = A[18] & A[24]; + c2 = A[12] ^ kt; + kt = A[24] | A[ 0]; + c3 = A[18] ^ kt; + kt = A[ 0] & A[ 6]; + c4 = A[24] ^ kt; + A[ 0] = c0; + A[ 6] = c1; + A[12] = c2; + A[18] = c3; + A[24] = c4; + bnn = ~A[22]; + kt = A[ 9] | A[10]; + c0 = A[ 3] ^ kt; + kt = A[10] & A[16]; + c1 = A[ 9] ^ kt; + kt = A[16] | bnn; + c2 = A[10] ^ kt; + kt = A[22] | A[ 3]; + c3 = A[16] ^ kt; + kt = A[ 3] & A[ 9]; + c4 = A[22] ^ kt; + A[ 3] = c0; + A[ 9] = c1; + A[10] = c2; + A[16] = c3; + A[22] = c4; + bnn = ~A[19]; + kt = A[ 7] | A[13]; + c0 = A[ 1] ^ kt; + kt = A[13] & A[19]; + c1 = A[ 7] ^ kt; + kt = bnn & A[20]; + c2 = A[13] ^ kt; + kt = A[20] | A[ 1]; + c3 = bnn ^ kt; + kt = A[ 1] & A[ 7]; + c4 = A[20] ^ kt; + A[ 1] = c0; + A[ 7] = c1; + A[13] = c2; + A[19] = c3; + A[20] = c4; + bnn = ~A[17]; + kt = A[ 5] & A[11]; + c0 = A[ 4] ^ kt; + kt = A[11] | A[17]; + c1 = A[ 5] ^ kt; + kt = bnn | A[23]; + c2 = A[11] ^ kt; + kt = A[23] & A[ 4]; + c3 = bnn ^ kt; + kt = A[ 4] | A[ 5]; + c4 = A[23] ^ kt; + A[ 4] = c0; + A[ 5] = c1; + A[11] = c2; + A[17] = c3; + A[23] = c4; + bnn = ~A[ 8]; + kt = bnn & A[14]; + c0 = A[ 2] ^ kt; + kt = A[14] | A[15]; + c1 = bnn ^ kt; + kt = A[15] & A[21]; + c2 = A[14] ^ kt; + kt = A[21] | A[ 2]; + c3 = A[15] ^ kt; + kt = A[ 2] & A[ 8]; + c4 = A[21] ^ kt; + A[ 2] = c0; + A[ 8] = c1; + A[14] = c2; + A[15] = c3; + A[21] = c4; + A[ 0] = A[ 0] ^ RC[j + 0]; + + tt0 = A[ 6] ^ A[ 9]; + tt1 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[24] ^ A[22]; + tt3 = A[20] ^ A[23]; + tt0 ^= A[21]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[12] ^ A[10]; + tt1 = A[13] ^ A[11]; + tt0 ^= A[14] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 3]; + tt3 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[18] ^ A[16]; + tt1 = A[19] ^ A[17]; + tt0 ^= A[15] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 6] ^ A[ 9]; + tt3 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[24] ^ A[22]; + tt1 = A[20] ^ A[23]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[12] ^ A[10]; + tt3 = A[13] ^ A[11]; + tt0 ^= A[14]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 3]; + tt1 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[18] ^ A[16]; + tt3 = A[19] ^ A[17]; + tt0 ^= A[15]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 3] = A[ 3] ^ t0; + A[ 1] = A[ 1] ^ t0; + A[ 4] = A[ 4] ^ t0; + A[ 2] = A[ 2] ^ t0; + A[ 6] = A[ 6] ^ t1; + A[ 9] = A[ 9] ^ t1; + A[ 7] = A[ 7] ^ t1; + A[ 5] = A[ 5] ^ t1; + A[ 8] = A[ 8] ^ t1; + A[12] = A[12] ^ t2; + A[10] = A[10] ^ t2; + A[13] = A[13] ^ t2; + A[11] = A[11] ^ t2; + A[14] = A[14] ^ t2; + A[18] = A[18] ^ t3; + A[16] = A[16] ^ t3; + A[19] = A[19] ^ t3; + A[17] = A[17] ^ t3; + A[15] = A[15] ^ t3; + A[24] = A[24] ^ t4; + A[22] = A[22] ^ t4; + A[20] = A[20] ^ t4; + A[23] = A[23] ^ t4; + A[21] = A[21] ^ t4; + A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36)); + A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3)); + A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41)); + A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18)); + A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1)); + A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44)); + A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10)); + A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45)); + A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2)); + A[12] = (A[12] << 62) | (A[12] >>> (64 - 62)); + A[10] = (A[10] << 6) | (A[10] >>> (64 - 6)); + A[13] = (A[13] << 43) | (A[13] >>> (64 - 43)); + A[11] = (A[11] << 15) | (A[11] >>> (64 - 15)); + A[14] = (A[14] << 61) | (A[14] >>> (64 - 61)); + A[18] = (A[18] << 28) | (A[18] >>> (64 - 28)); + A[16] = (A[16] << 55) | (A[16] >>> (64 - 55)); + A[19] = (A[19] << 25) | (A[19] >>> (64 - 25)); + A[17] = (A[17] << 21) | (A[17] >>> (64 - 21)); + A[15] = (A[15] << 56) | (A[15] >>> (64 - 56)); + A[24] = (A[24] << 27) | (A[24] >>> (64 - 27)); + A[22] = (A[22] << 20) | (A[22] >>> (64 - 20)); + A[20] = (A[20] << 39) | (A[20] >>> (64 - 39)); + A[23] = (A[23] << 8) | (A[23] >>> (64 - 8)); + A[21] = (A[21] << 14) | (A[21] >>> (64 - 14)); + bnn = ~A[13]; + kt = A[ 9] | A[13]; + c0 = A[ 0] ^ kt; + kt = bnn | A[17]; + c1 = A[ 9] ^ kt; + kt = A[17] & A[21]; + c2 = A[13] ^ kt; + kt = A[21] | A[ 0]; + c3 = A[17] ^ kt; + kt = A[ 0] & A[ 9]; + c4 = A[21] ^ kt; + A[ 0] = c0; + A[ 9] = c1; + A[13] = c2; + A[17] = c3; + A[21] = c4; + bnn = ~A[14]; + kt = A[22] | A[ 1]; + c0 = A[18] ^ kt; + kt = A[ 1] & A[ 5]; + c1 = A[22] ^ kt; + kt = A[ 5] | bnn; + c2 = A[ 1] ^ kt; + kt = A[14] | A[18]; + c3 = A[ 5] ^ kt; + kt = A[18] & A[22]; + c4 = A[14] ^ kt; + A[18] = c0; + A[22] = c1; + A[ 1] = c2; + A[ 5] = c3; + A[14] = c4; + bnn = ~A[23]; + kt = A[10] | A[19]; + c0 = A[ 6] ^ kt; + kt = A[19] & A[23]; + c1 = A[10] ^ kt; + kt = bnn & A[ 2]; + c2 = A[19] ^ kt; + kt = A[ 2] | A[ 6]; + c3 = bnn ^ kt; + kt = A[ 6] & A[10]; + c4 = A[ 2] ^ kt; + A[ 6] = c0; + A[10] = c1; + A[19] = c2; + A[23] = c3; + A[ 2] = c4; + bnn = ~A[11]; + kt = A[ 3] & A[ 7]; + c0 = A[24] ^ kt; + kt = A[ 7] | A[11]; + c1 = A[ 3] ^ kt; + kt = bnn | A[15]; + c2 = A[ 7] ^ kt; + kt = A[15] & A[24]; + c3 = bnn ^ kt; + kt = A[24] | A[ 3]; + c4 = A[15] ^ kt; + A[24] = c0; + A[ 3] = c1; + A[ 7] = c2; + A[11] = c3; + A[15] = c4; + bnn = ~A[16]; + kt = bnn & A[20]; + c0 = A[12] ^ kt; + kt = A[20] | A[ 4]; + c1 = bnn ^ kt; + kt = A[ 4] & A[ 8]; + c2 = A[20] ^ kt; + kt = A[ 8] | A[12]; + c3 = A[ 4] ^ kt; + kt = A[12] & A[16]; + c4 = A[ 8] ^ kt; + A[12] = c0; + A[16] = c1; + A[20] = c2; + A[ 4] = c3; + A[ 8] = c4; + A[ 0] = A[ 0] ^ RC[j + 1]; + t = A[ 5]; + A[ 5] = A[18]; + A[18] = A[11]; + A[11] = A[10]; + A[10] = A[ 6]; + A[ 6] = A[22]; + A[22] = A[20]; + A[20] = A[12]; + A[12] = A[19]; + A[19] = A[15]; + A[15] = A[24]; + A[24] = A[ 8]; + A[ 8] = t; + t = A[ 1]; + A[ 1] = A[ 9]; + A[ 9] = A[14]; + A[14] = A[ 2]; + A[ 2] = A[13]; + A[13] = A[23]; + A[23] = A[ 4]; + A[ 4] = A[21]; + A[21] = A[16]; + A[16] = A[ 3]; + A[ 3] = A[17]; + A[17] = A[ 7]; + A[ 7] = t; + } + } + + protected void doPadding(byte[] out, int off) + { + int ptr = flush(); + byte[] buf = getBlockBuffer(); + if ((ptr + 1) == buf.length) { + buf[ptr] = (byte)0x81; + } else { + buf[ptr] = (byte)0x01; + for (int i = ptr + 1; i < (buf.length - 1); i ++) + buf[i] = 0; + buf[buf.length - 1] = (byte)0x80; + } + processBlock(buf); + A[ 1] = ~A[ 1]; + A[ 2] = ~A[ 2]; + A[ 8] = ~A[ 8]; + A[12] = ~A[12]; + A[17] = ~A[17]; + A[20] = ~A[20]; + int dlen = engineGetDigestLength(); + for (int i = 0; i < dlen; i += 8) + encodeLELong(A[i >>> 3], tmpOut, i); + System.arraycopy(tmpOut, 0, out, off, dlen); + } + + protected void doInit() + { + A = new long[25]; + tmpOut = new byte[(engineGetDigestLength() + 7) & ~7]; + doReset(); + } + + public int getBlockLength() + { + return 200 - 2 * engineGetDigestLength(); + } + + private final void doReset() + { + for (int i = 0; i < 25; i ++) + A[i] = 0; + A[ 1] = 0xFFFFFFFFFFFFFFFFL; + A[ 2] = 0xFFFFFFFFFFFFFFFFL; + A[ 8] = 0xFFFFFFFFFFFFFFFFL; + A[12] = 0xFFFFFFFFFFFFFFFFL; + A[17] = 0xFFFFFFFFFFFFFFFFL; + A[20] = 0xFFFFFFFFFFFFFFFFL; + } + + protected Digest copyState(KeccakCore dst) + { + System.arraycopy(A, 0, dst.A, 0, 25); + return super.copyState(dst); + } + + public String toString() + { + return "Keccak-" + (engineGetDigestLength() << 3); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/jce/ECKeyAgreement.java b/crypto/src/main/java/org/platon/crypto/jce/ECKeyAgreement.java new file mode 100644 index 0000000..fc2bc26 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/jce/ECKeyAgreement.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.jce; + +import javax.crypto.KeyAgreement; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; + +public final class ECKeyAgreement { + + public static final String ALGORITHM = "ECDH"; + + private static final String algorithmAssertionMsg = + "Assumed the JRE supports EC key agreement"; + + private ECKeyAgreement() { } + + public static KeyAgreement getInstance() { + try { + return KeyAgreement.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } + + public static KeyAgreement getInstance(final String provider) throws NoSuchProviderException { + try { + return KeyAgreement.getInstance(ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } + + public static KeyAgreement getInstance(final Provider provider) { + try { + return KeyAgreement.getInstance(ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/jce/ECKeyFactory.java b/crypto/src/main/java/org/platon/crypto/jce/ECKeyFactory.java new file mode 100644 index 0000000..7898f1e --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/jce/ECKeyFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.jce; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; + +public final class ECKeyFactory { + + public static final String ALGORITHM = "EC"; + public static final String ENCRYPT_ALGORITHM = "ECIES"; + + private static final String algorithmAssertionMsg = + "Assumed the JRE supports EC key factories"; + + private ECKeyFactory() { } + + private static class Holder { + private static final KeyFactory INSTANCE; + + static { + try { + INSTANCE = KeyFactory.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } + } + + public static KeyFactory getInstance() { + return Holder.INSTANCE; + } + + public static KeyFactory getInstance(final String provider) throws NoSuchProviderException { + try { + return KeyFactory.getInstance(ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } + + public static KeyFactory getInstance(final Provider provider) { + try { + return KeyFactory.getInstance(ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/jce/ECKeyPairGenerator.java b/crypto/src/main/java/org/platon/crypto/jce/ECKeyPairGenerator.java new file mode 100644 index 0000000..a03c045 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/jce/ECKeyPairGenerator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.jce; + +import java.security.*; +import java.security.spec.ECGenParameterSpec; + +public final class ECKeyPairGenerator { + + public static final String ALGORITHM = "EC"; + public static final String CURVE_NAME = "secp256k1"; + + private static final String algorithmAssertionMsg = + "Assumed JRE supports EC key pair generation"; + + private static final String keySpecAssertionMsg = + "Assumed correct key spec statically"; + + private static final ECGenParameterSpec SECP256K1_CURVE + = new ECGenParameterSpec(CURVE_NAME); + + private ECKeyPairGenerator() { } + + private static class Holder { + private static final KeyPairGenerator INSTANCE; + + static { + try { + INSTANCE = KeyPairGenerator.getInstance(ALGORITHM); + INSTANCE.initialize(SECP256K1_CURVE); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } catch (InvalidAlgorithmParameterException ex) { + throw new AssertionError(keySpecAssertionMsg, ex); + } + } + } + + public static KeyPair generateKeyPair() { + return Holder.INSTANCE.generateKeyPair(); + } + + public static KeyPairGenerator getInstance(final String provider, final SecureRandom random) throws NoSuchProviderException { + try { + final KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM, provider); + gen.initialize(SECP256K1_CURVE, random); + return gen; + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } catch (InvalidAlgorithmParameterException ex) { + throw new AssertionError(keySpecAssertionMsg, ex); + } + } + + public static KeyPairGenerator getInstance(final Provider provider, final SecureRandom random) { + try { + final KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM, provider); + gen.initialize(SECP256K1_CURVE, random); + return gen; + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(algorithmAssertionMsg, ex); + } catch (InvalidAlgorithmParameterException ex) { + throw new AssertionError(keySpecAssertionMsg, ex); + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/jce/ECSignatureFactory.java b/crypto/src/main/java/org/platon/crypto/jce/ECSignatureFactory.java new file mode 100644 index 0000000..bc6b286 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/jce/ECSignatureFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.jce; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Signature; + +public final class ECSignatureFactory { + + public static final String RAW_ALGORITHM = "NONEwithECDSA"; + + private static final String rawAlgorithmAssertionMsg = + "Assumed the JRE supports NONEwithECDSA signatures"; + + private ECSignatureFactory() { } + + public static Signature getRawInstance() { + try { + return Signature.getInstance(RAW_ALGORITHM); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(rawAlgorithmAssertionMsg, ex); + } + } + + public static Signature getRawInstance(final String provider) throws NoSuchProviderException { + try { + return Signature.getInstance(RAW_ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(rawAlgorithmAssertionMsg, ex); + } + } + + public static Signature getRawInstance(final Provider provider) { + try { + return Signature.getInstance(RAW_ALGORITHM, provider); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(rawAlgorithmAssertionMsg, ex); + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/jce/NewBouncyCastleProvider.java b/crypto/src/main/java/org/platon/crypto/jce/NewBouncyCastleProvider.java new file mode 100644 index 0000000..75b8140 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/jce/NewBouncyCastleProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.jce; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.Provider; +import java.security.Security; + +public final class NewBouncyCastleProvider { + + private static class Holder { + private static final Provider INSTANCE; + static{ + Provider p = Security.getProvider("BC"); + + INSTANCE = (p != null) ? p : new BouncyCastleProvider(); + + INSTANCE.put("MessageDigest.ETH-KECCAK-256", "org.platon.crypto.hash.Keccak256"); + INSTANCE.put("MessageDigest.ETH-KECCAK-512", "org.platon.crypto.hash.Keccak512"); + } + } + + public static Provider getInstance() { + return Holder.INSTANCE; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/BN128.java b/crypto/src/main/java/org/platon/crypto/zksnark/BN128.java new file mode 100644 index 0000000..6ab75a5 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/BN128.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +/** + * Implementation of Barreto–Naehrig curve defined over abstract finite field. This curve is one of the keys to zkSNARKs.
+ * This specific curve was introduced in + * libff + * and used by a proving system in + * ZCash protocol
+ *
+ * + * Curve equation:
+ * Y^2 = X^3 + b, where "b" is a constant number belonging to corresponding specific field
+ * Point at infinity is encoded as (0, 0, 0)
+ *
+ * + * This curve has embedding degree 12 with respect to "r" (see {@link Params#R}), which means that "r" is a multiple of "p ^ 12 - 1", + * this condition is important for pairing operation implemented in {@link PairingCheck}
+ *
+ * + * Code of curve arithmetic has been ported from + * libff
+ *
+ * + * Current implementation uses Jacobian coordinate system as + * libff does, + * use {@link #toEthNotation()} to convert Jacobian coords to Ethereum encoding
+ * + * @author Mikhail Kalinin + * @since 05.09.2017 + */ +public abstract class BN128> { + + protected T x; + protected T y; + protected T z; + + protected BN128(T x, T y, T z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Point at infinity in Ethereum notation: should return (0; 0; 0), + * {@link #isZero()} method called for that point, also, returns {@code true} + */ + abstract protected BN128 zero(); + abstract protected BN128 instance(T x, T y, T z); + abstract protected T b(); + abstract protected T one(); + + /** + * Transforms given Jacobian to affine coordinates and then creates a point + */ + public BN128 toAffine() { + + if (isZero()) { + BN128 zero = zero(); + return instance(zero.x, one(), zero.z); // (0; 1; 0) + } + + T zInv = z.inverse(); + T zInv2 = zInv.squared(); + T zInv3 = zInv2.mul(zInv); + + T ax = x.mul(zInv2); + T ay = y.mul(zInv3); + + return instance(ax, ay, one()); + } + + /** + * Runs affine transformation and encodes point at infinity as (0; 0; 0) + */ + public BN128 toEthNotation() { + BN128 affine = toAffine(); + + // affine zero is (0; 1; 0), convert to Ethereum zero: (0; 0; 0) + if (affine.isZero()) { + return zero(); + } else { + return affine; + } + } + + protected boolean isOnCurve() { + + if (isZero()) return true; + + T z6 = z.squared().mul(z).squared(); + + T left = y.squared(); // y^2 + T right = x.squared().mul(x).add(b().mul(z6)); // x^3 + b * z^6 + return left.equals(right); + } + + public BN128 add(BN128 o) { + + if (this.isZero()) return o; // 0 + P = P + if (o.isZero()) return this; // P + 0 = P + + T x1 = this.x, y1 = this.y, z1 = this.z; + T x2 = o.x, y2 = o.y, z2 = o.z; + + // ported code is started from here + // next calculations are done in Jacobian coordinates + + T z1z1 = z1.squared(); + T z2z2 = z2.squared(); + + T u1 = x1.mul(z2z2); + T u2 = x2.mul(z1z1); + + T z1Cubed = z1.mul(z1z1); + T z2Cubed = z2.mul(z2z2); + + T s1 = y1.mul(z2Cubed); // s1 = y1 * Z2^3 + T s2 = y2.mul(z1Cubed); // s2 = y2 * Z1^3 + + if (u1.equals(u2) && s1.equals(s2)) { + return dbl(); // P + P = 2P + } + + T h = u2.sub(u1); // h = u2 - u1 + T i = h.dbl().squared(); // i = (2 * h)^2 + T j = h.mul(i); // j = h * i + T r = s2.sub(s1).dbl(); // r = 2 * (s2 - s1) + T v = u1.mul(i); // v = u1 * i + T zz = z1.add(z2).squared() + .sub(z1.squared()).sub(z2.squared()); + + T x3 = r.squared().sub(j).sub(v.dbl()); // x3 = r^2 - j - 2 * v + T y3 = v.sub(x3).mul(r).sub(s1.mul(j).dbl()); // y3 = r * (v - x3) - 2 * (s1 * j) + T z3 = zz.mul(h); // z3 = ((z1+z2)^2 - z1^2 - z2^2) * h = zz * h + + return instance(x3, y3, z3); + } + + public BN128 mul(BigInteger s) { + + if (s.compareTo(BigInteger.ZERO) == 0) // P * 0 = 0 + return zero(); + + if (isZero()) return this; // 0 * s = 0 + + BN128 res = zero(); + + for (int i = s.bitLength() - 1; i >= 0; i--) { + + res = res.dbl(); + + if (s.testBit(i)) { + res = res.add(this); + } + } + + return res; + } + + private BN128 dbl() { + + if (isZero()) return this; + + // ported code is started from here + // next calculations are done in Jacobian coordinates with z = 1 + + T a = x.squared(); // a = x^2 + T b = y.squared(); // b = y^2 + T c = b.squared(); // c = b^2 + T d = x.add(b).squared().sub(a).sub(c); + d = d.add(d); // d = 2 * ((x + b)^2 - a - c) + T e = a.add(a).add(a); // e = 3 * a + T f = e.squared(); // f = e^2 + + T x3 = f.sub(d.add(d)); // rx = f - 2 * d + T y3 = e.mul(d.sub(x3)).sub(c.dbl().dbl().dbl()); // ry = e * (d - rx) - 8 * c + T z3 = y.mul(z).dbl(); // z3 = 2 * y * z + + return instance(x3, y3, z3); + } + + public T x() { + return x; + } + + public T y() { + return y; + } + + public boolean isZero() { + return z.isZero(); + } + + protected boolean isValid() { + + // check whether coordinates belongs to the Field + if (!x.isValid() || !y.isValid() || !z.isValid()) { + return false; + } + + // check whether point is on the curve + if (!isOnCurve()) { + return false; + } + + return true; + } + + @Override + public String toString() { + return String.format("(%s; %s; %s)", x.toString(), y.toString(), z.toString()); + } + + @Override + @SuppressWarnings("all") + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BN128)) return false; + + BN128 bn128 = (BN128) o; + + if (x != null ? !x.equals(bn128.x) : bn128.x != null) return false; + if (y != null ? !y.equals(bn128.y) : bn128.y != null) return false; + return !(z != null ? !z.equals(bn128.z) : bn128.z != null); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp.java b/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp.java new file mode 100644 index 0000000..d7d91b2 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + + +import static org.platon.crypto.zksnark.Params.B_Fp; + +/** + * Definition of {@link BN128} over F_p, where "p" equals {@link Params#P}
+ * + * Curve equation:
+ * Y^2 = X^3 + b, where "b" equals {@link Params#B_Fp}
+ * + * @author Mikhail Kalinin + * @since 21.08.2017 + */ +public class BN128Fp extends BN128 { + + // the point at infinity + static final BN128 ZERO = new BN128Fp(Fp.ZERO, Fp.ZERO, Fp.ZERO); + + protected BN128Fp(Fp x, Fp y, Fp z) { + super(x, y, z); + } + + @Override + protected BN128 zero() { + return ZERO; + } + + @Override + protected BN128 instance(Fp x, Fp y, Fp z) { + return new BN128Fp(x, y, z); + } + + @Override + protected Fp b() { + return B_Fp; + } + + @Override + protected Fp one() { + return Fp._1; + } + + /** + * Checks whether x and y belong to Fp, + * then checks whether point with (x; y) coordinates lays on the curve. + * + * Returns new point if all checks have been passed, + * otherwise returns null + */ + public static BN128 create(byte[] xx, byte[] yy) { + + Fp x = Fp.create(xx); + Fp y = Fp.create(yy); + + // check for point at infinity + if (x.isZero() && y.isZero()) { + return ZERO; + } + + BN128 p = new BN128Fp(x, y, Fp._1); + + // check whether point is a valid one + if (p.isValid()) { + return p; + } else { + return null; + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp2.java b/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp2.java new file mode 100644 index 0000000..1a07440 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/BN128Fp2.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +import static org.platon.crypto.zksnark.Params.B_Fp2; + + +/** + * Definition of {@link BN128} over F_p2, where "p" equals {@link Params#P}
+ * + * Curve equation:
+ * Y^2 = X^3 + b, where "b" equals {@link Params#B_Fp2}
+ * + * @author Mikhail Kalinin + * @since 31.08.2017 + */ +public class BN128Fp2 extends BN128 { + + // the point at infinity + static final BN128 ZERO = new BN128Fp2(Fp2.ZERO, Fp2.ZERO, Fp2.ZERO); + + protected BN128Fp2(Fp2 x, Fp2 y, Fp2 z) { + super(x, y, z); + } + + @Override + protected BN128 zero() { + return ZERO; + } + + @Override + protected BN128 instance(Fp2 x, Fp2 y, Fp2 z) { + return new BN128Fp2(x, y, z); + } + + @Override + protected Fp2 b() { + return B_Fp2; + } + + @Override + protected Fp2 one() { + return Fp2._1; + } + + protected BN128Fp2(BigInteger a, BigInteger b, BigInteger c, BigInteger d) { + super(Fp2.create(a, b), Fp2.create(c, d), Fp2._1); + } + + /** + * Checks whether provided data are coordinates of a point on the curve, + * then checks if this point is a member of subgroup of order "r" + * and if checks have been passed it returns a point, otherwise returns null + */ + public static BN128 create(byte[] aa, byte[] bb, byte[] cc, byte[] dd) { + + Fp2 x = Fp2.create(aa, bb); + Fp2 y = Fp2.create(cc, dd); + + // check for point at infinity + if (x.isZero() && y.isZero()) { + return ZERO; + } + + BN128 p = new BN128Fp2(x, y, Fp2._1); + + // check whether point is a valid one + if (p.isValid()) { + return p; + } else { + return null; + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/BN128G1.java b/crypto/src/main/java/org/platon/crypto/zksnark/BN128G1.java new file mode 100644 index 0000000..b52c4a5 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/BN128G1.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +/** + * Implementation of specific cyclic subgroup of points belonging to {@link BN128Fp}
+ * Members of this subgroup are passed as a first param to pairing input {@link PairingCheck#addPair(BN128G1, BN128G2)}
+ * + * Subgroup generator G = (1; 2) + * + * @author Mikhail Kalinin + * @since 01.09.2017 + */ +public class BN128G1 extends BN128Fp { + + BN128G1(BN128 p) { + super(p.x, p.y, p.z); + } + + @Override + public BN128G1 toAffine() { + return new BN128G1(super.toAffine()); + } + + /** + * Checks whether point is a member of subgroup, + * returns a point if check has been passed and null otherwise + */ + public static BN128G1 create(byte[] x, byte[] y) { + + BN128 p = BN128Fp.create(x, y); + + if (p == null) return null; + + if (!isGroupMember(p)) return null; + + return new BN128G1(p); + } + + /** + * Formally we have to do this check + * but in our domain it's not necessary, + * thus always return true + */ + private static boolean isGroupMember(BN128 p) { + return true; + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/BN128G2.java b/crypto/src/main/java/org/platon/crypto/zksnark/BN128G2.java new file mode 100644 index 0000000..357d7fb --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/BN128G2.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +import static org.platon.crypto.zksnark.Params.*; + +/** + * Implementation of specific cyclic subgroup of points belonging to {@link BN128Fp2}
+ * Members of this subgroup are passed as a second param to pairing input {@link PairingCheck#addPair(BN128G1, BN128G2)}
+ *
+ * + * The order of subgroup is {@link Params#R}
+ * Generator of subgroup G =
+ * (11559732032986387107991004021392285783925812861821192530917403151452391805634 * i +
+ * 10857046999023057135944570762232829481370756359578518086990519993285655852781,
+ * 4082367875863433681332203403145435568316851327593401208105741076214120093531 * i +
+ * 8495653923123431417604973247489272438418190587263600148770280649306958101930)
+ *
+ * + * @author Mikhail Kalinin + * @since 31.08.2017 + */ +public class BN128G2 extends BN128Fp2 { + + BN128G2(BN128 p) { + super(p.x, p.y, p.z); + } + + BN128G2(Fp2 x, Fp2 y, Fp2 z) { + super(x, y, z); + } + + @Override + public BN128G2 toAffine() { + return new BN128G2(super.toAffine()); + } + + /** + * Checks whether provided data are coordinates of a point belonging to subgroup, + * if check has been passed it returns a point, otherwise returns null + */ + public static BN128G2 create(byte[] a, byte[] b, byte[] c, byte[] d) { + + BN128 p = BN128Fp2.create(a, b, c, d); + + // fails if point is invalid + if (p == null) { + return null; + } + + // check whether point is a subgroup member + if (!isGroupMember(p)) return null; + + return new BN128G2(p); + } + + private static boolean isGroupMember(BN128 p) { + BN128 left = p.mul(FR_NEG_ONE).add(p); + return left.isZero(); // should satisfy condition: -1 * p + p == 0, where -1 belongs to F_r + } + static final BigInteger FR_NEG_ONE = BigInteger.ONE.negate().mod(R); + + BN128G2 mulByP() { + + Fp2 rx = TWIST_MUL_BY_P_X.mul(x.frobeniusMap(1)); + Fp2 ry = TWIST_MUL_BY_P_Y.mul(y.frobeniusMap(1)); + Fp2 rz = z.frobeniusMap(1); + + return new BN128G2(rx, ry, rz); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Field.java b/crypto/src/main/java/org/platon/crypto/zksnark/Field.java new file mode 100644 index 0000000..06428fc --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Field.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +/** + * Interface of abstract finite field + * + * @author Mikhail Kalinin + * @since 05.09.2017 + */ +interface Field { + + T add(T o); + T mul(T o); + T sub(T o); + T squared(); + T dbl(); + T inverse(); + T negate(); + boolean isZero(); + boolean isValid(); +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Fp.java b/crypto/src/main/java/org/platon/crypto/zksnark/Fp.java new file mode 100644 index 0000000..eee1c38 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Fp.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +import static org.platon.crypto.zksnark.Params.P; + +/** + * Arithmetic in F_p, p = 21888242871839275222246405745257275088696311157297823662689037894645226208583 + * + * @author Mikhail Kalinin + * @since 01.09.2017 + */ +public class Fp implements Field { + + static final Fp ZERO = new Fp(BigInteger.ZERO); + static final Fp _1 = new Fp(BigInteger.ONE); + static final Fp NON_RESIDUE = new Fp(new BigInteger("21888242871839275222246405745257275088696311157297823662689037894645226208582")); + + static final Fp _2_INV = new Fp(BigInteger.valueOf(2).modInverse(P)); + + BigInteger v; + + Fp(BigInteger v) { this.v = v; } + + @Override public Fp add(Fp o) { return new Fp(this.v.add(o.v).mod(P)); } + @Override public Fp mul(Fp o) { return new Fp(this.v.multiply(o.v).mod(P)); } + @Override public Fp sub(Fp o) { return new Fp(this.v.subtract(o.v).mod(P)); } + @Override public Fp squared() { return new Fp(v.multiply(v).mod(P)); } + @Override public Fp dbl() { return new Fp(v.add(v).mod(P)); } + @Override public Fp inverse() { return new Fp(v.modInverse(P)); } + @Override public Fp negate() { return new Fp(v.negate().mod(P)); } + @Override public boolean isZero() { return v.compareTo(BigInteger.ZERO) == 0; } + + /** + * Checks if provided value is a valid Fp member + */ + @Override + public boolean isValid() { + return v.compareTo(P) < 0; + } + + Fp2 mul(Fp2 o) { return new Fp2(o.a.mul(this), o.b.mul(this)); } + + static Fp create(byte[] v) { + return new Fp(new BigInteger(1, v)); + } + + static Fp create(BigInteger v) { + return new Fp(v); + } + + public byte[] bytes() { + return v.toByteArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Fp fp = (Fp) o; + + return !(v != null ? v.compareTo(fp.v) != 0 : fp.v != null); + } + + @Override + public String toString() { + return v.toString(); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Fp12.java b/crypto/src/main/java/org/platon/crypto/zksnark/Fp12.java new file mode 100644 index 0000000..941eff1 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Fp12.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +/** + * Arithmetic in Fp_12
+ *
+ * + * "p" equals 21888242871839275222246405745257275088696311157297823662689037894645226208583,
+ * elements of Fp_12 are represented with 2 elements of {@link Fp6}
+ *
+ * + * Field arithmetic is ported from libff + * + * @author Mikhail Kalinin + * @since 02.09.2017 + */ +class Fp12 implements Field { + + static final Fp12 ZERO = new Fp12(Fp6.ZERO, Fp6.ZERO); + static final Fp12 _1 = new Fp12(Fp6._1, Fp6.ZERO); + + Fp6 a; + Fp6 b; + + Fp12 (Fp6 a, Fp6 b) { + this.a = a; + this.b = b; + } + + @Override + public Fp12 squared() { + + Fp6 ab = a.mul(b); + + Fp6 ra = a.add(b).mul(a.add(b.mulByNonResidue())).sub(ab).sub(ab.mulByNonResidue()); + Fp6 rb = ab.add(ab); + + return new Fp12(ra, rb); + } + + @Override + public Fp12 dbl() { + return null; + } + + Fp12 mulBy024(Fp2 ell0, Fp2 ellVW, Fp2 ellVV) { + + Fp2 z0 = a.a; + Fp2 z1 = a.b; + Fp2 z2 = a.c; + Fp2 z3 = b.a; + Fp2 z4 = b.b; + Fp2 z5 = b.c; + + Fp2 x0 = ell0; + Fp2 x2 = ellVV; + Fp2 x4 = ellVW; + + Fp2 t0, t1, t2, s0, t3, t4, d0, d2, d4, s1; + + d0 = z0.mul(x0); + d2 = z2.mul(x2); + d4 = z4.mul(x4); + t2 = z0.add(z4); + t1 = z0.add(z2); + s0 = z1.add(z3).add(z5); + + // For z.a_.a_ = z0. + s1 = z1.mul(x2); + t3 = s1.add(d4); + t4 = Fp6.NON_RESIDUE.mul(t3).add(d0); + z0 = t4; + + // For z.a_.b_ = z1 + t3 = z5.mul(x4); + s1 = s1.add(t3); + t3 = t3.add(d2); + t4 = Fp6.NON_RESIDUE.mul(t3); + t3 = z1.mul(x0); + s1 = s1.add(t3); + t4 = t4.add(t3); + z1 = t4; + + // For z.a_.c_ = z2 + t0 = x0.add(x2); + t3 = t1.mul(t0).sub(d0).sub(d2); + t4 = z3.mul(x4); + s1 = s1.add(t4); + t3 = t3.add(t4); + + // For z.b_.a_ = z3 (z3 needs z2) + t0 = z2.add(z4); + z2 = t3; + t1 = x2.add(x4); + t3 = t0.mul(t1).sub(d2).sub(d4); + t4 = Fp6.NON_RESIDUE.mul(t3); + t3 = z3.mul(x0); + s1 = s1.add(t3); + t4 = t4.add(t3); + z3 = t4; + + // For z.b_.b_ = z4 + t3 = z5.mul(x2); + s1 = s1.add(t3); + t4 = Fp6.NON_RESIDUE.mul(t3); + t0 = x0.add(x4); + t3 = t2.mul(t0).sub(d0).sub(d4); + t4 = t4.add(t3); + z4 = t4; + + // For z.b_.c_ = z5. + t0 = x0.add(x2).add(x4); + t3 = s0.mul(t0).sub(s1); + z5 = t3; + + return new Fp12(new Fp6(z0, z1, z2), new Fp6(z3, z4, z5)); + } + + @Override + public Fp12 add(Fp12 o) { + return new Fp12(a.add(o.a), b.add(o.b)); + } + + @Override + public Fp12 mul(Fp12 o) { + + Fp6 a2 = o.a, b2 = o.b; + Fp6 a1 = a, b1 = b; + + Fp6 a1a2 = a1.mul(a2); + Fp6 b1b2 = b1.mul(b2); + + Fp6 ra = a1a2.add(b1b2.mulByNonResidue()); + Fp6 rb = a1.add(b1).mul(a2.add(b2)).sub(a1a2).sub(b1b2); + + return new Fp12(ra, rb); + } + + @Override + public Fp12 sub(Fp12 o) { + return new Fp12(a.sub(o.a), b.sub(o.b)); + } + + @Override + public Fp12 inverse() { + + Fp6 t0 = a.squared(); + Fp6 t1 = b.squared(); + Fp6 t2 = t0.sub(t1.mulByNonResidue()); + Fp6 t3 = t2.inverse(); + + Fp6 ra = a.mul(t3); + Fp6 rb = b.mul(t3).negate(); + + return new Fp12(ra, rb); + } + + @Override + public Fp12 negate() { + return new Fp12(a.negate(), b.negate()); + } + + @Override + public boolean isZero() { + return this.equals(ZERO); + } + + @Override + public boolean isValid() { + return a.isValid() && b.isValid(); + } + + Fp12 frobeniusMap(int power) { + + Fp6 ra = a.frobeniusMap(power); + Fp6 rb = b.frobeniusMap(power).mul(FROBENIUS_COEFFS_B[power % 12]); + + return new Fp12(ra, rb); + } + + Fp12 cyclotomicSquared() { + + Fp2 z0 = a.a; + Fp2 z4 = a.b; + Fp2 z3 = a.c; + Fp2 z2 = b.a; + Fp2 z1 = b.b; + Fp2 z5 = b.c; + + Fp2 t0, t1, t2, t3, t4, t5, tmp; + + // t0 + t1*y = (z0 + z1*y)^2 = a^2 + tmp = z0.mul(z1); + t0 = z0.add(z1).mul(z0.add(Fp6.NON_RESIDUE.mul(z1))).sub(tmp).sub(Fp6.NON_RESIDUE.mul(tmp)); + t1 = tmp.add(tmp); + // t2 + t3*y = (z2 + z3*y)^2 = b^2 + tmp = z2.mul(z3); + t2 = z2.add(z3).mul(z2.add(Fp6.NON_RESIDUE.mul(z3))).sub(tmp).sub(Fp6.NON_RESIDUE.mul(tmp)); + t3 = tmp.add(tmp); + // t4 + t5*y = (z4 + z5*y)^2 = c^2 + tmp = z4.mul(z5); + t4 = z4.add(z5).mul(z4.add(Fp6.NON_RESIDUE.mul(z5))).sub(tmp).sub(Fp6.NON_RESIDUE.mul(tmp)); + t5 = tmp.add(tmp); + + // for A + + // z0 = 3 * t0 - 2 * z0 + z0 = t0.sub(z0); + z0 = z0.add(z0); + z0 = z0.add(t0); + // z1 = 3 * t1 + 2 * z1 + z1 = t1.add(z1); + z1 = z1.add(z1); + z1 = z1.add(t1); + + // for B + + // z2 = 3 * (xi * t5) + 2 * z2 + tmp = Fp6.NON_RESIDUE.mul(t5); + z2 = tmp.add(z2); + z2 = z2.add(z2); + z2 = z2.add(tmp); + + // z3 = 3 * t4 - 2 * z3 + z3 = t4.sub(z3); + z3 = z3.add(z3); + z3 = z3.add(t4); + + // for C + + // z4 = 3 * t2 - 2 * z4 + z4 = t2.sub(z4); + z4 = z4.add(z4); + z4 = z4.add(t2); + + // z5 = 3 * t3 + 2 * z5 + z5 = t3.add(z5); + z5 = z5.add(z5); + z5 = z5.add(t3); + + return new Fp12(new Fp6(z0, z4, z3), new Fp6(z2, z1, z5)); + } + + Fp12 cyclotomicExp(BigInteger pow) { + + Fp12 res = _1; + + for (int i = pow.bitLength() - 1; i >=0; i--) { + res = res.cyclotomicSquared(); + + if (pow.testBit(i)) { + res = res.mul(this); + } + } + + return res; + } + + Fp12 unitaryInverse() { + + Fp6 ra = a; + Fp6 rb = b.negate(); + + return new Fp12(ra, rb); + } + + Fp12 negExp(BigInteger exp) { + return this.cyclotomicExp(exp).unitaryInverse(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Fp12)) return false; + + Fp12 fp12 = (Fp12) o; + + if (a != null ? !a.equals(fp12.a) : fp12.a != null) return false; + return !(b != null ? !b.equals(fp12.b) : fp12.b != null); + + } + + @Override + public String toString() { + return String.format( + "Fp12 (%s; %s)\n" + + " (%s; %s)\n" + + " (%s; %s)\n" + + " (%s; %s)\n" + + " (%s; %s)\n" + + " (%s; %s)\n", + + a.a.a, a.a.b, + a.b.a, a.b.b, + a.c.a, a.c.b, + b.a.a, b.a.b, + b.b.a, b.b.b, + b.c.a, b.c.b + ); + } + + static final Fp2[] FROBENIUS_COEFFS_B = new Fp2[] { + + new Fp2(BigInteger.ONE, + BigInteger.ZERO), + + new Fp2(new BigInteger("8376118865763821496583973867626364092589906065868298776909617916018768340080"), + new BigInteger("16469823323077808223889137241176536799009286646108169935659301613961712198316")), + + new Fp2(new BigInteger("21888242871839275220042445260109153167277707414472061641714758635765020556617"), + BigInteger.ZERO), + + new Fp2(new BigInteger("11697423496358154304825782922584725312912383441159505038794027105778954184319"), + new BigInteger("303847389135065887422783454877609941456349188919719272345083954437860409601")), + + new Fp2(new BigInteger("21888242871839275220042445260109153167277707414472061641714758635765020556616"), + BigInteger.ZERO), + + new Fp2(new BigInteger("3321304630594332808241809054958361220322477375291206261884409189760185844239"), + new BigInteger("5722266937896532885780051958958348231143373700109372999374820235121374419868")), + + new Fp2(new BigInteger("21888242871839275222246405745257275088696311157297823662689037894645226208582"), + BigInteger.ZERO), + + new Fp2(new BigInteger("13512124006075453725662431877630910996106405091429524885779419978626457868503"), + new BigInteger("5418419548761466998357268504080738289687024511189653727029736280683514010267")), + + new Fp2(new BigInteger("2203960485148121921418603742825762020974279258880205651966"), + BigInteger.ZERO), + + new Fp2(new BigInteger("10190819375481120917420622822672549775783927716138318623895010788866272024264"), + new BigInteger("21584395482704209334823622290379665147239961968378104390343953940207365798982")), + + new Fp2(new BigInteger("2203960485148121921418603742825762020974279258880205651967"), + BigInteger.ZERO), + + new Fp2(new BigInteger("18566938241244942414004596690298913868373833782006617400804628704885040364344"), + new BigInteger("16165975933942742336466353786298926857552937457188450663314217659523851788715")) + }; +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Fp2.java b/crypto/src/main/java/org/platon/crypto/zksnark/Fp2.java new file mode 100644 index 0000000..f222178 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Fp2.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +/** + * Arithmetic in F_p2
+ *
+ * + * "p" equals 21888242871839275222246405745257275088696311157297823662689037894645226208583, + * elements of F_p2 are represented as a polynomials "a * i + b" modulo "i^2 + 1" from the ring F_p[i]
+ *
+ * + * Field arithmetic is ported from libff
+ * + * @author Mikhail Kalinin + * @since 01.09.2017 + */ +class Fp2 implements Field { + + static final Fp2 ZERO = new Fp2(Fp.ZERO, Fp.ZERO); + static final Fp2 _1 = new Fp2(Fp._1, Fp.ZERO); + static final Fp2 NON_RESIDUE = new Fp2(BigInteger.valueOf(9), BigInteger.ONE); + + static final Fp[] FROBENIUS_COEFFS_B = new Fp[] { + new Fp(BigInteger.ONE), + new Fp(new BigInteger("21888242871839275222246405745257275088696311157297823662689037894645226208582")) + }; + + Fp a; + Fp b; + + Fp2(Fp a, Fp b) { + this.a = a; + this.b = b; + } + + Fp2(BigInteger a, BigInteger b) { + this(new Fp(a), new Fp(b)); + } + + @Override + public Fp2 squared() { + + // using Complex squaring + + Fp ab = a.mul(b); + + Fp ra = a.add(b).mul(b.mul(Fp.NON_RESIDUE).add(a)) + .sub(ab).sub(ab.mul(Fp.NON_RESIDUE)); // ra = (a + b)(a + NON_RESIDUE * b) - ab - NON_RESIDUE * b + Fp rb = ab.dbl(); + + return new Fp2(ra, rb); + } + + @Override + public Fp2 mul(Fp2 o) { + + Fp aa = a.mul(o.a); + Fp bb = b.mul(o.b); + + Fp ra = bb.mul(Fp.NON_RESIDUE).add(aa); // ra = a1 * a2 + NON_RESIDUE * b1 * b2 + Fp rb = a.add(b).mul(o.a.add(o.b)).sub(aa).sub(bb); // rb = (a1 + b1)(a2 + b2) - a1 * a2 - b1 * b2 + + return new Fp2(ra, rb); + } + + @Override + public Fp2 add(Fp2 o) { + return new Fp2(a.add(o.a), b.add(o.b)); + } + + @Override + public Fp2 sub(Fp2 o) { + return new Fp2(a.sub(o.a), b.sub(o.b)); + } + + @Override + public Fp2 dbl() { + return this.add(this); + } + + @Override + public Fp2 inverse() { + + Fp t0 = a.squared(); + Fp t1 = b.squared(); + Fp t2 = t0.sub(Fp.NON_RESIDUE.mul(t1)); + Fp t3 = t2.inverse(); + + Fp ra = a.mul(t3); // ra = a * t3 + Fp rb = b.mul(t3).negate(); // rb = -(b * t3) + + return new Fp2(ra, rb); + } + + @Override + public Fp2 negate() { + return new Fp2(a.negate(), b.negate()); + } + + @Override + public boolean isZero() { + return this.equals(ZERO); + } + + @Override + public boolean isValid() { + return a.isValid() && b.isValid(); + } + + static Fp2 create(BigInteger aa, BigInteger bb) { + + Fp a = Fp.create(aa); + Fp b = Fp.create(bb); + + return new Fp2(a, b); + } + + static Fp2 create(byte[] aa, byte[] bb) { + + Fp a = Fp.create(aa); + Fp b = Fp.create(bb); + + return new Fp2(a, b); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Fp2 fp2 = (Fp2) o; + + if (a != null ? !a.equals(fp2.a) : fp2.a != null) return false; + return !(b != null ? !b.equals(fp2.b) : fp2.b != null); + + } + + Fp2 frobeniusMap(int power) { + + Fp ra = a; + Fp rb = FROBENIUS_COEFFS_B[power % 2].mul(b); + + return new Fp2(ra, rb); + } + + Fp2 mulByNonResidue() { + return NON_RESIDUE.mul(this); + } + + @Override + public String toString() { + return String.format("%si + %s", a.toString(), b.toString()); + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Fp6.java b/crypto/src/main/java/org/platon/crypto/zksnark/Fp6.java new file mode 100644 index 0000000..3f7fcb7 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Fp6.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +/** + * Arithmetic in Fp_6
+ *
+ * + * "p" equals 21888242871839275222246405745257275088696311157297823662689037894645226208583,
+ * elements of Fp_6 are represented with 3 elements of {@link Fp2}
+ *
+ * + * Field arithmetic is ported from libff + * + * @author Mikhail Kalinin + * @since 05.09.2017 + */ +class Fp6 implements Field { + + static final Fp6 ZERO = new Fp6(Fp2.ZERO, Fp2.ZERO, Fp2.ZERO); + static final Fp6 _1 = new Fp6(Fp2._1, Fp2.ZERO, Fp2.ZERO); + static final Fp2 NON_RESIDUE = new Fp2(BigInteger.valueOf(9), BigInteger.ONE); + + Fp2 a; + Fp2 b; + Fp2 c; + + Fp6(Fp2 a, Fp2 b, Fp2 c) { + this.a = a; + this.b = b; + this.c = c; + } + + @Override + public Fp6 squared() { + + Fp2 s0 = a.squared(); + Fp2 ab = a.mul(b); + Fp2 s1 = ab.dbl(); + Fp2 s2 = a.sub(b).add(c).squared(); + Fp2 bc = b.mul(c); + Fp2 s3 = bc.dbl(); + Fp2 s4 = c.squared(); + + Fp2 ra = s0.add(s3.mulByNonResidue()); + Fp2 rb = s1.add(s4.mulByNonResidue()); + Fp2 rc = s1.add(s2).add(s3).sub(s0).sub(s4); + + return new Fp6(ra, rb, rc); + } + + @Override + public Fp6 dbl() { + return this.add(this); + } + + @Override + public Fp6 mul(Fp6 o) { + + Fp2 a1 = a, b1 = b, c1 = c; + Fp2 a2 = o.a, b2 = o.b, c2 = o.c; + + Fp2 a1a2 = a1.mul(a2); + Fp2 b1b2 = b1.mul(b2); + Fp2 c1c2 = c1.mul(c2); + + Fp2 ra = a1a2.add(b1.add(c1).mul(b2.add(c2)).sub(b1b2).sub(c1c2).mulByNonResidue()); + Fp2 rb = a1.add(b1).mul(a2.add(b2)).sub(a1a2).sub(b1b2).add(c1c2.mulByNonResidue()); + Fp2 rc = a1.add(c1).mul(a2.add(c2)).sub(a1a2).add(b1b2).sub(c1c2); + + return new Fp6(ra, rb, rc); + } + + Fp6 mul(Fp2 o) { + + Fp2 ra = a.mul(o); + Fp2 rb = b.mul(o); + Fp2 rc = c.mul(o); + + return new Fp6(ra, rb, rc); + } + + Fp6 mulByNonResidue() { + + Fp2 ra = NON_RESIDUE.mul(c); + Fp2 rb = a; + Fp2 rc = b; + + return new Fp6(ra, rb, rc); + } + + @Override + public Fp6 add(Fp6 o) { + + Fp2 ra = a.add(o.a); + Fp2 rb = b.add(o.b); + Fp2 rc = c.add(o.c); + + return new Fp6(ra, rb, rc); + } + + @Override + public Fp6 sub(Fp6 o) { + + Fp2 ra = a.sub(o.a); + Fp2 rb = b.sub(o.b); + Fp2 rc = c.sub(o.c); + + return new Fp6(ra, rb, rc); + } + + @Override + public Fp6 inverse() { + + /* From "High-Speed Software Implementation of the Optimal Ate Pairing over Barreto-Naehrig Curves"; Algorithm 17 */ + + Fp2 t0 = a.squared(); + Fp2 t1 = b.squared(); + Fp2 t2 = c.squared(); + Fp2 t3 = a.mul(b); + Fp2 t4 = a.mul(c); + Fp2 t5 = b.mul(c); + Fp2 c0 = t0.sub(t5.mulByNonResidue()); + Fp2 c1 = t2.mulByNonResidue().sub(t3); + Fp2 c2 = t1.sub(t4); // typo in paper referenced above. should be "-" as per Scott, but is "*" + Fp2 t6 = a.mul(c0).add((c.mul(c1).add(b.mul(c2))).mulByNonResidue()).inverse(); + + Fp2 ra = t6.mul(c0); + Fp2 rb = t6.mul(c1); + Fp2 rc = t6.mul(c2); + + return new Fp6(ra, rb, rc); + } + + @Override + public Fp6 negate() { + return new Fp6(a.negate(), b.negate(), c.negate()); + } + + @Override + public boolean isZero() { + return this.equals(ZERO); + } + + @Override + public boolean isValid() { + return a.isValid() && b.isValid() && c.isValid(); + } + + Fp6 frobeniusMap(int power) { + + Fp2 ra = a.frobeniusMap(power); + Fp2 rb = FROBENIUS_COEFFS_B[power % 6].mul(b.frobeniusMap(power)); + Fp2 rc = FROBENIUS_COEFFS_C[power % 6].mul(c.frobeniusMap(power)); + + return new Fp6(ra, rb, rc); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Fp6)) return false; + + Fp6 fp6 = (Fp6) o; + + if (a != null ? !a.equals(fp6.a) : fp6.a != null) return false; + if (b != null ? !b.equals(fp6.b) : fp6.b != null) return false; + return !(c != null ? !c.equals(fp6.c) : fp6.c != null); + } + + static final Fp2[] FROBENIUS_COEFFS_B = { + + new Fp2(BigInteger.ONE, + BigInteger.ZERO), + + new Fp2(new BigInteger("21575463638280843010398324269430826099269044274347216827212613867836435027261"), + new BigInteger("10307601595873709700152284273816112264069230130616436755625194854815875713954")), + + new Fp2(new BigInteger("21888242871839275220042445260109153167277707414472061641714758635765020556616"), + BigInteger.ZERO), + + new Fp2(new BigInteger("3772000881919853776433695186713858239009073593817195771773381919316419345261"), + new BigInteger("2236595495967245188281701248203181795121068902605861227855261137820944008926")), + + new Fp2(new BigInteger("2203960485148121921418603742825762020974279258880205651966"), + BigInteger.ZERO), + + new Fp2(new BigInteger("18429021223477853657660792034369865839114504446431234726392080002137598044644"), + new BigInteger("9344045779998320333812420223237981029506012124075525679208581902008406485703")) + }; + + static final Fp2[] FROBENIUS_COEFFS_C = { + + new Fp2(BigInteger.ONE, + BigInteger.ZERO), + + new Fp2(new BigInteger("2581911344467009335267311115468803099551665605076196740867805258568234346338"), + new BigInteger("19937756971775647987995932169929341994314640652964949448313374472400716661030")), + + new Fp2(new BigInteger("2203960485148121921418603742825762020974279258880205651966"), + BigInteger.ZERO), + + new Fp2(new BigInteger("5324479202449903542726783395506214481928257762400643279780343368557297135718"), + new BigInteger("16208900380737693084919495127334387981393726419856888799917914180988844123039")), + + new Fp2(new BigInteger("21888242871839275220042445260109153167277707414472061641714758635765020556616"), + BigInteger.ZERO), + + new Fp2(new BigInteger("13981852324922362344252311234282257507216387789820983642040889267519694726527"), + new BigInteger("7629828391165209371577384193250820201684255241773809077146787135900891633097")) + }; +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/PairingCheck.java b/crypto/src/main/java/org/platon/crypto/zksnark/PairingCheck.java new file mode 100644 index 0000000..b91ccde --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/PairingCheck.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.platon.crypto.zksnark.Params.*; + +/** + * Implementation of a Pairing Check operation over points of two twisted Barreto–Naehrig curves {@link BN128Fp}, {@link BN128Fp2}
+ *
+ * + * The Pairing itself is a transformation of the form G1 x G2 -> Gt,
+ * where G1 and G2 are members of {@link BN128G1} {@link BN128G2} respectively,
+ * Gt is a subgroup of roots of unity in {@link Fp12} field, root degree equals to {@link Params#R}
+ *
+ * + * Pairing Check input is a sequence of point pairs, the result is either 1 or 0, 1 is considered as success, 0 as fail
+ *
+ * + * Usage: + *
    + *
  • add pairs sequentially with {@link #addPair(BN128G1, BN128G2)}
  • + *
  • run check with {@link #run()} after all paris have been added
  • + *
  • get result with {@link #result()}
  • + *
+ * + * Arithmetic has been ported from libff + * Ate pairing algorithms + * + * @author Mikhail Kalinin + * @since 01.09.2017 + */ +public class PairingCheck { + + static final BigInteger LOOP_COUNT = new BigInteger("29793968203157093288"); + + List pairs = new ArrayList<>(); + Fp12 product = Fp12._1; + + private PairingCheck() {} + + public static PairingCheck create() { + return new PairingCheck(); + } + + public void addPair(BN128G1 g1, BN128G2 g2) { + pairs.add(Pair.of(g1, g2)); + } + + public void run() { + + for (Pair pair : pairs) { + + Fp12 miller = pair.millerLoop(); + + if (!miller.equals(Fp12._1)) // run mul code only if necessary + product = product.mul(miller); + } + + // finalize + product = finalExponentiation(product); + } + + public int result() { + return product.equals(Fp12._1) ? 1 : 0; + } + + private static Fp12 millerLoop(BN128G1 g1, BN128G2 g2) { + + // convert to affine coordinates + g1 = g1.toAffine(); + g2 = g2.toAffine(); + + // calculate Ell coefficients + List coeffs = calcEllCoeffs(g2); + + Fp12 f = Fp12._1; + int idx = 0; + + // for each bit except most significant one + for (int i = LOOP_COUNT.bitLength() - 2; i >=0; i--) { + + EllCoeffs c = coeffs.get(idx++); + f = f.squared(); + f = f.mulBy024(c.ell0, g1.y.mul(c.ellVW), g1.x.mul(c.ellVV)); + + if (LOOP_COUNT.testBit(i)) { + c = coeffs.get(idx++); + f = f.mulBy024(c.ell0, g1.y.mul(c.ellVW), g1.x.mul(c.ellVV)); + } + + } + + EllCoeffs c = coeffs.get(idx++); + f = f.mulBy024(c.ell0, g1.y.mul(c.ellVW), g1.x.mul(c.ellVV)); + + c = coeffs.get(idx); + f = f.mulBy024(c.ell0, g1.y.mul(c.ellVW), g1.x.mul(c.ellVV)); + + return f; + } + + private static List calcEllCoeffs(BN128G2 base) { + + List coeffs = new ArrayList<>(); + + BN128G2 addend = base; + + // for each bit except most significant one + for (int i = LOOP_COUNT.bitLength() - 2; i >=0; i--) { + + Precomputed doubling = flippedMillerLoopDoubling(addend); + + addend = doubling.g2; + coeffs.add(doubling.coeffs); + + if (LOOP_COUNT.testBit(i)) { + Precomputed addition = flippedMillerLoopMixedAddition(base, addend); + addend = addition.g2; + coeffs.add(addition.coeffs); + } + } + + BN128G2 q1 = base.mulByP(); + BN128G2 q2 = q1.mulByP(); + + q2 = new BN128G2(q2.x, q2.y.negate(), q2.z) ; // q2.y = -q2.y + + Precomputed addition = flippedMillerLoopMixedAddition(q1, addend); + addend = addition.g2; + coeffs.add(addition.coeffs); + + addition = flippedMillerLoopMixedAddition(q2, addend); + coeffs.add(addition.coeffs); + + return coeffs; + } + + private static Precomputed flippedMillerLoopMixedAddition(BN128G2 base, BN128G2 addend) { + + Fp2 x1 = addend.x, y1 = addend.y, z1 = addend.z; + Fp2 x2 = base.x, y2 = base.y; + + Fp2 d = x1.sub(x2.mul(z1)); // d = x1 - x2 * z1 + Fp2 e = y1.sub(y2.mul(z1)); // e = y1 - y2 * z1 + Fp2 f = d.squared(); // f = d^2 + Fp2 g = e.squared(); // g = e^2 + Fp2 h = d.mul(f); // h = d * f + Fp2 i = x1.mul(f); // i = x1 * f + Fp2 j = h.add(z1.mul(g)).sub(i.dbl()); // j = h + z1 * g - 2 * i + + Fp2 x3 = d.mul(j); // x3 = d * j + Fp2 y3 = e.mul(i.sub(j)).sub(h.mul(y1)); // y3 = e * (i - j) - h * y1) + Fp2 z3 = z1.mul(h); // z3 = Z1*H + + Fp2 ell0 = TWIST.mul(e.mul(x2).sub(d.mul(y2))); // ell_0 = TWIST * (e * x2 - d * y2) + Fp2 ellVV = e.negate(); // ell_VV = -e + Fp2 ellVW = d; // ell_VW = d + + return Precomputed.of( + new BN128G2(x3, y3, z3), + new EllCoeffs(ell0, ellVW, ellVV) + ); + } + + private static Precomputed flippedMillerLoopDoubling(BN128G2 g2) { + + Fp2 x = g2.x, y = g2.y, z = g2.z; + + Fp2 a = Fp._2_INV.mul(x.mul(y)); // a = x * y / 2 + Fp2 b = y.squared(); // b = y^2 + Fp2 c = z.squared(); // c = z^2 + Fp2 d = c.add(c).add(c); // d = 3 * c + Fp2 e = B_Fp2.mul(d); // e = twist_b * d + Fp2 f = e.add(e).add(e); // f = 3 * e + Fp2 g = Fp._2_INV.mul(b.add(f)); // g = (b + f) / 2 + Fp2 h = y.add(z).squared().sub(b.add(c)); // h = (y + z)^2 - (b + c) + Fp2 i = e.sub(b); // i = e - b + Fp2 j = x.squared(); // j = x^2 + Fp2 e2 = e.squared(); // e2 = e^2 + + Fp2 rx = a.mul(b.sub(f)); // rx = a * (b - f) + Fp2 ry = g.squared().sub(e2.add(e2).add(e2)); // ry = g^2 - 3 * e^2 + Fp2 rz = b.mul(h); // rz = b * h + + Fp2 ell0 = TWIST.mul(i); // ell_0 = twist * i + Fp2 ellVW = h.negate(); // ell_VW = -h + Fp2 ellVV = j.add(j).add(j); // ell_VV = 3 * j + + return Precomputed.of( + new BN128G2(rx, ry, rz), + new EllCoeffs(ell0, ellVW, ellVV) + ); + } + + public static Fp12 finalExponentiation(Fp12 el) { + + // first chunk + Fp12 w = new Fp12(el.a, el.b.negate()); // el.b = -el.b + Fp12 x = el.inverse(); + Fp12 y = w.mul(x); + Fp12 z = y.frobeniusMap(2); + Fp12 pre = z.mul(y); + + // last chunk + Fp12 a = pre.negExp(PAIRING_FINAL_EXPONENT_Z); + Fp12 b = a.cyclotomicSquared(); + Fp12 c = b.cyclotomicSquared(); + Fp12 d = c.mul(b); + Fp12 e = d.negExp(PAIRING_FINAL_EXPONENT_Z); + Fp12 f = e.cyclotomicSquared(); + Fp12 g = f.negExp(PAIRING_FINAL_EXPONENT_Z); + Fp12 h = d.unitaryInverse(); + Fp12 i = g.unitaryInverse(); + Fp12 j = i.mul(e); + Fp12 k = j.mul(h); + Fp12 l = k.mul(b); + Fp12 m = k.mul(e); + Fp12 n = m.mul(pre); + Fp12 o = l.frobeniusMap(1); + Fp12 p = o.mul(n); + Fp12 q = k.frobeniusMap(2); + Fp12 r = q.mul(p); + Fp12 s = pre.unitaryInverse(); + Fp12 t = s.mul(l); + Fp12 u = t.frobeniusMap(3); + Fp12 v = u.mul(r); + + return v; + } + + static class Precomputed { + + BN128G2 g2; + EllCoeffs coeffs; + + static Precomputed of(BN128G2 g2, EllCoeffs coeffs) { + return new Precomputed(g2, coeffs); + } + + Precomputed(BN128G2 g2, EllCoeffs coeffs) { + this.g2 = g2; + this.coeffs = coeffs; + } + } + + static class Pair { + + BN128G1 g1; + BN128G2 g2; + + static Pair of(BN128G1 g1, BN128G2 g2) { + return new Pair(g1, g2); + } + + Pair(BN128G1 g1, BN128G2 g2) { + this.g1 = g1; + this.g2 = g2; + } + + Fp12 millerLoop() { + + // miller loop result equals "1" if at least one of the points is zero + if (g1.isZero()) return Fp12._1; + if (g2.isZero()) return Fp12._1; + + return PairingCheck.millerLoop(g1, g2); + } + } + + static class EllCoeffs { + Fp2 ell0; + Fp2 ellVW; + Fp2 ellVV; + + EllCoeffs(Fp2 ell0, Fp2 ellVW, Fp2 ellVV) { + this.ell0 = ell0; + this.ellVW = ellVW; + this.ellVV = ellVV; + } + } +} diff --git a/crypto/src/main/java/org/platon/crypto/zksnark/Params.java b/crypto/src/main/java/org/platon/crypto/zksnark/Params.java new file mode 100644 index 0000000..2b65cb4 --- /dev/null +++ b/crypto/src/main/java/org/platon/crypto/zksnark/Params.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.platon.crypto.zksnark; + +import java.math.BigInteger; + +/** + * Common params for BN curves, its derivatives and pairing + * + * @author Mikhail Kalinin + * @since 31.08.2017 + */ +class Params { + + /** + * "p" field parameter of F_p, F_p2, F_p6 and F_p12 + */ + static final BigInteger P = new BigInteger("21888242871839275222246405745257275088696311157297823662689037894645226208583"); + + /** + * "r" order of {@link BN128G2} cyclic subgroup + */ + static final BigInteger R = new BigInteger("21888242871839275222246405745257275088548364400416034343698204186575808495617"); + + /** + * "b" curve parameter for {@link BN128Fp} + */ + static final Fp B_Fp = Fp.create(BigInteger.valueOf(3)); + + /** + * Twist parameter for the curves + */ + static final Fp2 TWIST = Fp2.create(BigInteger.valueOf(9), BigInteger.valueOf(1)); + + /** + * "b" curve parameter for {@link BN128Fp2} + */ + static final Fp2 B_Fp2 = B_Fp.mul(TWIST.inverse()); + + static final Fp2 TWIST_MUL_BY_P_X = Fp2.create( + new BigInteger("21575463638280843010398324269430826099269044274347216827212613867836435027261"), + new BigInteger("10307601595873709700152284273816112264069230130616436755625194854815875713954") + ); + + static final Fp2 TWIST_MUL_BY_P_Y = Fp2.create( + new BigInteger("2821565182194536844548159561693502659359617185244120367078079554186484126554"), + new BigInteger("3505843767911556378687030309984248845540243509899259641013678093033130930403") + ); + + static final BigInteger PAIRING_FINAL_EXPONENT_Z = new BigInteger("4965661367192848881"); +} diff --git a/crypto/src/main/resources/logback.xml b/crypto/src/main/resources/logback.xml new file mode 100644 index 0000000..8efc9df --- /dev/null +++ b/crypto/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg %n + + + DEBUG + + + + + ../logs/crypto.log + + ../logs/crypto_%d{yyyy-MM-dd}.log + 30 + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n + + + + + + + + + + + + \ No newline at end of file diff --git a/crypto/src/test/java/org/platon/crypto/HashUtilTest.java b/crypto/src/test/java/org/platon/crypto/HashUtilTest.java new file mode 100644 index 0000000..b2e4868 --- /dev/null +++ b/crypto/src/test/java/org/platon/crypto/HashUtilTest.java @@ -0,0 +1,27 @@ +package org.platon.crypto; + +import org.junit.Assert; +import org.junit.Test; +import org.bouncycastle.util.encoders.Hex; + +public class HashUtilTest { + + private static final String message = "welcome to platon"; + + private static final String messageSha3Str = "86139924b67294e70849375a7b59531c0819e790471a607578b162211ca44871"; + + private static final String messageR160Str = "89af04763c4b9c3cb447af31b5e0f5756bddc76d"; + + @Test + public void sha3() { + byte[] messageSha3= HashUtil.sha3(message.getBytes()); + Assert.assertEquals(Hex.toHexString(messageSha3),messageSha3Str); + } + + @Test + public void ripemd160() { + byte[] messageR160= HashUtil.ripemd160(message.getBytes()); + Assert.assertEquals(Hex.toHexString(messageR160),messageR160Str); + } + +} \ No newline at end of file diff --git a/crypto/src/test/java/org/platon/crypto/WalletUtilTest.java b/crypto/src/test/java/org/platon/crypto/WalletUtilTest.java new file mode 100644 index 0000000..de598d7 --- /dev/null +++ b/crypto/src/test/java/org/platon/crypto/WalletUtilTest.java @@ -0,0 +1,117 @@ +package org.platon.crypto; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.SignatureException; + +public class WalletUtilTest { + + private static final Logger logger = LoggerFactory.getLogger(WalletUtilTest.class); + + private static final String message = "welcome to platon"; + + private static final String defWalletFileName = "5f2ae1c60a3038956cf3355960cb211de78bab50"; + + private static final String defAddress = "5f2ae1c60a3038956cf3355960cb211de78bab50"; + + private static final String walletFilePath = Thread.currentThread().getContextClassLoader().getResource("keys").getPath(); + + @Test + public void generateECKey() { + ECKey ecKey = WalletUtil.generateECKey(); + logECKey(ecKey); + Assert.assertNotNull(ecKey); + } + + @Test + public void signAndVerify() { + try { + byte[] messageHash = HashUtil.sha3(message.getBytes()); + logger.debug("messageHash:{}", Hex.toHexString(messageHash)); + ECKey ecKey = WalletUtil.loadWallet(walletFilePath + File.separator + defWalletFileName); + logECKey(ecKey); + byte[] sign = WalletUtil.sign(messageHash, ecKey); + logger.debug("sign:{}", new String(sign)); + Assert.assertTrue(WalletUtil.verify(messageHash, sign, Hex.decode(defAddress))); + } catch (Exception e) { + logger.error("signAndVerify error:", e); + } + } + + /** + * get address from signature and message + * + * @throws SignatureException + */ + @Test + public void signatureToAddress() throws SignatureException { + ECKey ecKey = WalletUtil.loadWallet(walletFilePath + File.separator + defWalletFileName); + logECKey(ecKey); + byte[] messageHash = HashUtil.sha3(message.getBytes()); + byte[] sign = WalletUtil.sign(messageHash, ecKey); + logger.debug("sign:{}", new String(sign)); + byte[] address = WalletUtil.signatureToAddress(messageHash, sign); + Assert.assertArrayEquals(ecKey.getAddress(), address); + } + + @Test + public void signatureStringToAddress() throws SignatureException { + ECKey ecKey = WalletUtil.loadWallet(walletFilePath + File.separator + defWalletFileName); + logECKey(ecKey); + byte[] messageHash = HashUtil.sha3(message.getBytes()); + byte[] sign = WalletUtil.sign(messageHash, ecKey); + logger.debug("sign:{}", new String(sign)); + byte[] address = WalletUtil.signatureToAddress(messageHash, new String(sign)); + Assert.assertArrayEquals(ecKey.getAddress(), address); + } + + @Test + public void signatureCover() throws SignatureException { + ECKey ecKey = WalletUtil.loadWallet(walletFilePath + File.separator + defWalletFileName); + logECKey(ecKey); + byte[] messageHash = HashUtil.sha3(message.getBytes()); + byte[] sign = WalletUtil.sign(messageHash, ecKey); + logger.debug("sign:{}", new String(sign)); + ECKey.ECDSASignature signature = WalletUtil.signToECDSASignature(sign); + byte[] sign2 = WalletUtil.signToByteArray(signature); + Assert.assertArrayEquals(sign, sign2); + } + + /** + * Open when you need to test, will generate a test wallet in the "test/resources/" + */ + @Test + @Ignore + public void generateWallet() { + logger.info("walletFilePath:{}", walletFilePath); + ECKey ecKey = WalletUtil.generateWallet(walletFilePath); + logECKey(ecKey); + Assert.assertNotNull(ecKey); + } + + private void logECKey(ECKey ecKey) { + logger.debug("address is:{}", Hex.toHexString(ecKey.getAddress())); + logger.debug("privateKey is:{}", ecKey.getPrivKey()); + logger.debug("publicKey is:{}", ecKey.getPubKeyPoint().toString()); + } + + @Test + public void encrypt(){ + ECKey ecKey = WalletUtil.loadWallet(walletFilePath + File.separator + defWalletFileName); + byte[] initMessage = message.getBytes(StandardCharsets.UTF_8); + byte[] encryptMessage = WalletUtil.encrypt(initMessage,ecKey); + byte[] encryptMessageFromBytes = WalletUtil.encrypt(initMessage,ecKey.getPubKey()); + byte[] decryptMessage = WalletUtil.decrypt(encryptMessage,ecKey); + Assert.assertArrayEquals(initMessage,decryptMessage); + byte[] decryptMessageFromBytes = WalletUtil.decrypt(encryptMessageFromBytes,ecKey); + Assert.assertArrayEquals(initMessage,decryptMessageFromBytes); + } + +} \ No newline at end of file diff --git a/crypto/src/test/resources/keys/5f2ae1c60a3038956cf3355960cb211de78bab50 b/crypto/src/test/resources/keys/5f2ae1c60a3038956cf3355960cb211de78bab50 new file mode 100644 index 0000000..fe15591 --- /dev/null +++ b/crypto/src/test/resources/keys/5f2ae1c60a3038956cf3355960cb211de78bab50 @@ -0,0 +1 @@ +{"address":"5f2ae1c60a3038956cf3355960cb211de78bab50","privateKey":"4c63f0f5b5ef625cc5fc072eedc3dfa34a9a737489dbccf9b8e3050ea2b3cef2","publicKey":"04ee200f36d16241d7bf99e42d2d5f2239329dca7a5328f5f81f88b66c12dc478d6eef914e9a4eafadb0ad92b2f5904de1eba3549f0b82dbc07e0853cd4fe6438b"} \ No newline at end of file diff --git a/crypto/src/test/resources/logback.xml b/crypto/src/test/resources/logback.xml new file mode 100644 index 0000000..53bf7a6 --- /dev/null +++ b/crypto/src/test/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg %n + + + DEBUG + + + + + ../logs/crypto-test.log + + ../logs/crypto-test%d{yyyy-MM-dd}.log + 30 + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..05a5a55 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +// ********************************************************************** +// +// 配置扩展属性 +// +// ********************************************************************** + diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p2p/build.gradle b/p2p/build.gradle new file mode 100644 index 0000000..cffc351 --- /dev/null +++ b/p2p/build.gradle @@ -0,0 +1,30 @@ +archivesBaseName = "${rootProject.name}-p2p" + +dependencies { + compile project(":slice") + compile project(":common") + compile project(":crypto") + compile( + "commons-collections:commons-collections:3.2.2", + 'org.apache.commons:commons-configuration2:2.2', + "org.apache.commons:commons-lang3:3.5", + 'org.apache.httpcomponents:httpclient:4.5.3', + "commons-codec:commons-codec:1.10", + 'commons-configuration:commons-configuration:1.10', + 'org.lmdbjava:lmdbjava:0.6.0', + 'com.alibaba:fastjson:1.2.29' + ) + compile group: 'io.netty', name: 'netty-all', version: '4.1.28.Final' + + // https://mvnrepository.com/artifact/org.reflections/reflections + compile group: 'org.reflections', name: 'reflections', version: '0.9.10' +} + + +buildscript { + repositories { + maven { + url "http://maven.aliyun.com/nexus/content/groups/public/" + } + } +} diff --git a/p2p/src/main/java/org/platon/p2p/EccDecoder.java b/p2p/src/main/java/org/platon/p2p/EccDecoder.java new file mode 100644 index 0000000..e33e664 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/EccDecoder.java @@ -0,0 +1,56 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.common.CodecUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EccDecoder extends LengthFieldBasedFrameDecoder { + + private static final Logger logger = LoggerFactory.getLogger(EccDecoder.class); + + private ByteString remoteNodeId; + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + public EccDecoder(int maxFrameLength, + int lengthFieldOffset, int lengthFieldLength, + int lengthAdjustment, int initialBytesToStrip) { + super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + logger.debug("to decode message from remoteNodeId:={}, channel:{}", CodecUtils.toHexString(remoteNodeId), ctx.channel()); + + int length = in.readInt(); + if(in.readableBytes() < length){ + logger.error("cannot decode the message, message length is error"); + throw new Exception("cannot decode the message, message length is error"); + } + + ByteBuf buf = in.readBytes(length); + byte[] encrypted = new byte[buf.readableBytes()]; + buf.readBytes(encrypted); + buf.release(); + + if(NodeContext.ecKey==null){ + logger.error("cannot decode the message because the local ECKey is missed."); + throw new Exception("cannot decode the message because the local ECKey is missed."); + } + byte[] decrypted = WalletUtil.decrypt(encrypted, NodeContext.ecKey); + + return Unpooled.wrappedBuffer(decrypted); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/EccEncoder.java b/p2p/src/main/java/org/platon/p2p/EccEncoder.java new file mode 100644 index 0000000..e0684f9 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/EccEncoder.java @@ -0,0 +1,66 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.common.CodecUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EccEncoder extends MessageToByteEncoder { + + private static final Logger logger = LoggerFactory.getLogger(EccEncoder.class); + + private ByteString remoteNodeId; + private ByteString remoteNodePubKey; + + private final int lengthFieldLength; + + public EccEncoder(int lengthFieldLength) { + this.lengthFieldLength = lengthFieldLength; + } + + + public EccEncoder(ByteString remoteNodeId, ByteString remoteNodePubKey, int lengthFieldLength) { + this.remoteNodeId = remoteNodeId; + this.remoteNodePubKey = remoteNodePubKey; + this.lengthFieldLength = lengthFieldLength; + } + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + public ByteString getRemoteNodePubKey() { + return remoteNodePubKey; + } + + public void setRemoteNodePubKey(ByteString remoteNodePubKey) { + this.remoteNodePubKey = remoteNodePubKey; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception{ + logger.debug("to encode message to remoteNodeId:{}, channel:{}", CodecUtils.toHexString(remoteNodeId), ctx.channel() ); + byte[] dataBytes = ByteBufUtil.getBytes(in); + if(remoteNodePubKey!=null && !remoteNodePubKey.isEmpty()){ + byte[] encryptedBytes = WalletUtil.encrypt(dataBytes, remoteNodePubKey.toByteArray()); + if(lengthFieldLength==4){ + out.writeInt(encryptedBytes.length); + out.writeBytes(encryptedBytes); + }else{ + throw new IllegalArgumentException("length field only can be an integer"); + } + }else{ + logger.error("cannot find remote node's public key"); + throw new Exception("cannot find remote node's public key"); + } + } +} diff --git a/p2p/src/main/java/org/platon/p2p/ForwardMessageHook.java b/p2p/src/main/java/org/platon/p2p/ForwardMessageHook.java new file mode 100644 index 0000000..0b3b442 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/ForwardMessageHook.java @@ -0,0 +1,45 @@ +package org.platon.p2p; + +import com.google.protobuf.Any; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.platon.Header; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-26 18:17 + */ +public class ForwardMessageHook { + + private static Map hooks = new ConcurrentHashMap(); + + public static void add(String serviceName, ForwardMessageHook.ForwardMessageCallback hook){ + hooks.put(serviceName, hook); + } + + public static void remove(String serviceName) { + hooks.remove(serviceName); + } + + + + public static List nextHops(Header header, Any any) { + ForwardMessageHook.ForwardMessageCallback callback = hooks.get(any.getTypeUrl()); + + if (callback != null) { + return callback.nextHops(header, any); + } + return null; + } + + public static ForwardMessageHook.ForwardMessageCallback get(String serviceName) { + return hooks.get(serviceName); + } + + public interface ForwardMessageCallback { + List nextHops(Header header, Any any); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/MessageHook.java b/p2p/src/main/java/org/platon/p2p/MessageHook.java new file mode 100644 index 0000000..87bf4c0 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/MessageHook.java @@ -0,0 +1,44 @@ +package org.platon.p2p; + +import org.platon.p2p.proto.platon.PlatonMessage; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-23 13:59 + */ +public class MessageHook { + + + private static Map hooks = new ConcurrentHashMap(); + + public static void add(String serviceName, MessageCallback hook){ + hooks.put(serviceName, hook); + } + + public static void remove(String serviceName) { + hooks.remove(serviceName); + } + + + + public static boolean isNeedProcess(PlatonMessage request) { + + MessageCallback callback = hooks.get(request.getBody().getData().getTypeUrl()); + + if (callback != null) { + return callback.isNeedProcess(request); + } + return false; + } + + public static MessageCallback get(String serviceName) { + return hooks.get(serviceName); + } + + public interface MessageCallback { + boolean isNeedProcess(PlatonMessage request); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeClient.java b/p2p/src/main/java/org/platon/p2p/NodeClient.java new file mode 100644 index 0000000..7443a6e --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeClient.java @@ -0,0 +1,86 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultMessageSizeEstimator; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.common.PeerConfig; +import org.platon.p2p.common.PlatonMessageHelper; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/21, lvxiaoyi, Initial Version. + */ +public class NodeClient { + + private static Logger logger = LoggerFactory.getLogger(NodeClient.class); + + private static EventLoopGroup workerGroup; + + public NodeClient() { + workerGroup = new NioEventLoopGroup(0, new ThreadFactory() { + AtomicInteger cnt = new AtomicInteger(0); + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "NodeClientWorker-" + cnt.getAndIncrement()); + } + }); + } + + + public boolean connect(ByteString remoteId, String host, int port, ByteString remoteNodePubKey){ + ChannelFuture channelFuture = connectAsync(remoteId, host, port, remoteNodePubKey).syncUninterruptibly(); + if(channelFuture.isDone() && channelFuture.isSuccess()){ + logger.debug("success to connect remote node (nodeId=:{}, address:={}:{}", CodecUtils.toHexString(remoteId), host, port); + + SessionManager sessionManager = SpringContextUtil.getBean("sessionManager"); + sessionManager.createSession(remoteId, remoteNodePubKey, channelFuture.channel()); + + PlatonMessage platonMessage = PlatonMessageHelper.createCreateSession(); + + + channelFuture.channel().writeAndFlush(platonMessage); + + return true; + }else{ + logger.error("cannot connect to remote node (nodeId=:{}, address:={}:{})", CodecUtils.toHexString(remoteId), host, port); + return false; + } + } + + public ChannelFuture connectAsync(ByteString remoteId,String host, int port, ByteString remoteNodePubKey) { + Bootstrap b = new Bootstrap(); + b.group(workerGroup); + b.channel(NioSocketChannel.class); + + b.option(ChannelOption.SO_KEEPALIVE, true); + b.option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT); + b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, PeerConfig.getInstance().getPeerConnectTimeout()*1000); + + b.remoteAddress(host, port); + b.handler(new NodeClientChannelInitializer(remoteId, remoteNodePubKey)); + return b.connect(); + } + + public void shutdown() { + logger.info("Shutdown NodeClient"); + workerGroup.shutdownGracefully(); + workerGroup.terminationFuture().syncUninterruptibly(); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeClientChannelHandler.java b/p2p/src/main/java/org/platon/p2p/NodeClientChannelHandler.java new file mode 100644 index 0000000..0b0dc34 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeClientChannelHandler.java @@ -0,0 +1,36 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelHandlerContext; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.proto.session.CreateSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NodeClientChannelHandler extends P2pChannelHandler { + + private static final Logger logger = LoggerFactory.getLogger(NodeClientChannelHandler.class); + + private boolean isHandshakeDone = false; + + + private ByteString remoteNodePubKey; + + public NodeClientChannelHandler(ByteString remoteNodeId, ByteString remoteNodePubKey) { + super(); + this.remoteNodeId = remoteNodeId; + this.remoteNodePubKey = remoteNodePubKey; + } + + + @Override + protected void handleCreateSessionRequest(ChannelHandlerContext ctx, CreateSession createSession) { + + } + + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + logger.debug("channel is active for:" + CodecUtils.toHexString(remoteNodeId)); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeClientChannelInitializer.java b/p2p/src/main/java/org/platon/p2p/NodeClientChannelInitializer.java new file mode 100644 index 0000000..52adb5d --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeClientChannelInitializer.java @@ -0,0 +1,58 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.protobuf.ProtobufDecoder; +import io.netty.handler.codec.protobuf.ProtobufEncoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NodeClientChannelInitializer extends ChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(NodeClientChannelInitializer.class); + + + private final static int MAX_FRAME_LENGTH = 1024*1024*1024; + private final static int LENGTH_FIELD = 4; + private final static int LENGTH_FIELD_OFFSET = 0; + private final static int LENGTH_ADJUSTMENT = 0; + private final static int INITIAL_BYTES_TO_STRIP = 4; + + + private ByteString remoteNodeId; + private ByteString remoteNodePubKey; + + public NodeClientChannelInitializer(ByteString remoteNodeId, ByteString remoteNodePubKey) { + this.remoteNodeId = remoteNodeId; + this.remoteNodePubKey = remoteNodePubKey; + } + + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + + ChannelPipeline p = channel.pipeline(); + + p.addLast(new LoggingHandler(LogLevel.INFO)); + + p.addLast(new EccDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)); + + p.addLast(new ProtobufVarint32FrameDecoder()); + p.addLast(new ProtobufDecoder(PlatonMessage.getDefaultInstance())); + + + p.addLast(new EccEncoder(remoteNodeId, remoteNodePubKey, LENGTH_FIELD)); + + p.addLast(new ProtobufVarint32LengthFieldPrepender()); + p.addLast(new ProtobufEncoder()); + + p.addLast(new NodeClientChannelHandler(remoteNodeId, remoteNodePubKey)); + + } +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeContext.java b/p2p/src/main/java/org/platon/p2p/NodeContext.java new file mode 100644 index 0000000..663b98f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeContext.java @@ -0,0 +1,50 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import org.platon.crypto.ECKey; +import org.platon.p2p.proto.common.NodeID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/17, lvxiaoyi, Initial Version. + */ +public class NodeContext { + + private static Logger logger = LoggerFactory.getLogger(NodeContext.class); + + private static Map relayMap = new ConcurrentHashMap<>(); + + public static String host; + public static int port; + + public static ByteString localNodeId; + public static ByteString address; + + public static byte[] publicKey; + public static byte[] privateKey; + public static ECKey ecKey; + + public static long timeIntervalForDuplicatedMessage; + + public static Executor executor = Executors.newCachedThreadPool(); + + + public static String getEndpoint(){ + return host + ":" + port; + } + public static NodeID getNodeID(){ + return NodeID.newBuilder().setId(localNodeId).setEndpoint(host +":"+port).build(); + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeServer.java b/p2p/src/main/java/org/platon/p2p/NodeServer.java new file mode 100644 index 0000000..338ede9 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeServer.java @@ -0,0 +1,126 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import com.typesafe.config.Config; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import org.platon.common.config.NodeConfig; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.crypto.ECKey; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.common.PeerConfig; +import org.platon.p2p.plugins.KadTopologyPlugin; +import org.platon.p2p.proto.common.NodeID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.List; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/20, lvxiaoyi, Initial Version. + */ +public class NodeServer { + private static final Logger logger = LoggerFactory.getLogger(NodeServer.class); + + + + private EventLoopGroup bossGroup = new NioEventLoopGroup(); + + + private EventLoopGroup workerGroup = new NioEventLoopGroup(); + + + + public static void main(String[] args) throws Exception { + NodeServer nodeServer = new NodeServer(); + nodeServer.startup(); + } + + private void initContext(){ + NodeContext.host = NodeConfig.getInstance().getHost(); + NodeContext.port = PeerConfig.getInstance().getPort(); + NodeContext.privateKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPrivateKey()); + NodeContext.publicKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPublicKey()); + + NodeContext.ecKey = ECKey.fromPrivate(NodeContext.privateKey ); + NodeContext.localNodeId = ByteString.copyFrom(WalletUtil.computeAddress(NodeContext.publicKey)); + + NodeContext.timeIntervalForDuplicatedMessage = PeerConfig.getInstance().getTimeIntervalForDuplicatedMessage(); + + ApplicationContext context = new AnnotationConfigApplicationContext(SpringContextUtil.class); + + } + + public void startup() { + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run() { + shutdown(); + } + }); + + + logger.info("starting Node server ..."); + + initContext(); + + ServerBootstrap bootstrap = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new NodeServerChannelInitializer()); + + ChannelFuture channelFuture = bootstrap.bind(NodeContext.host, NodeContext.port).syncUninterruptibly(); + + channelFuture.channel().closeFuture().addListener( new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + shutdown(); + } + }); + + logger.info("Node server started, NodeId:={} / {}", CodecUtils.toHexString(NodeContext.localNodeId), NodeContext.getNodeID()); + System.out.println(String.format("Node server started, NodeId:%s / %s...", CodecUtils.toHexString(NodeContext.localNodeId), NodeContext.getNodeID())); + + + List configs = PeerConfig.getInstance().getActiveNodeConfig(); + configs.forEach(config -> { + String ip = config.getString("host"); + String port = config.getString("port"); + String publicKey = config.getString("public-key"); + + logger.info("connecting to active node:{}:{}", ip, port); + join(ip, port, publicKey); + } + ); + } + + private void join(String ip, String port, String pubKey){ + + byte[] remoteNodePubKeyBytes = Numeric.hexStringToByteArray(pubKey); + ByteString remoteNodeId = ByteString.copyFrom(WalletUtil.computeAddress(remoteNodePubKeyBytes)); + + NodeID remote = NodeID.newBuilder().setId(remoteNodeId).setEndpoint(ip+":"+port).setPubKey(ByteString.copyFrom(remoteNodePubKeyBytes)).build(); + + KadTopologyPlugin topologyPlugin = SpringContextUtil.getBean("topologyPlugin"); + + topologyPlugin.join(remote); + } + + private void shutdown() { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeServerChannelHandler.java b/p2p/src/main/java/org/platon/p2p/NodeServerChannelHandler.java new file mode 100644 index 0000000..0ab341c --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeServerChannelHandler.java @@ -0,0 +1,30 @@ +package org.platon.p2p; + +import io.netty.channel.ChannelHandlerContext; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.proto.session.CreateSession; +import org.platon.p2p.session.CreateSessionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/20, lvxiaoyi, Initial Version. + */ +public class NodeServerChannelHandler extends P2pChannelHandler { + + private static final Logger logger = LoggerFactory.getLogger(NodeServerChannelHandler.class); + + public NodeServerChannelHandler() { + super(); + } + + protected void handleCreateSessionRequest(ChannelHandlerContext ctx, CreateSession createSession){ + + CreateSessionHandler createSessionHandler = SpringContextUtil.getBean("createSessionHandler"); + createSessionHandler.handleCreateSessionRequest(createSession, ctx); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/NodeServerChannelInitializer.java b/p2p/src/main/java/org/platon/p2p/NodeServerChannelInitializer.java new file mode 100644 index 0000000..8368881 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/NodeServerChannelInitializer.java @@ -0,0 +1,55 @@ +package org.platon.p2p; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.protobuf.ProtobufDecoder; +import io.netty.handler.codec.protobuf.ProtobufEncoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/20, lvxiaoyi, Initial Version. + */ +public class NodeServerChannelInitializer extends ChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(NodeServerChannelInitializer.class); + + + private final static int MAX_FRAME_LENGTH = 1024*1024*1024; + private final static int LENGTH_FIELD = 4; + private final static int LENGTH_FIELD_OFFSET = 0; + private final static int LENGTH_ADJUSTMENT = 0; + private final static int INITIAL_BYTES_TO_STRIP = 4; + + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + + + ChannelPipeline p = channel.pipeline(); + + p.addLast(new LoggingHandler(LogLevel.INFO)); + + p.addLast("eccDecoder", new EccDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)); + + p.addLast(new ProtobufVarint32FrameDecoder()); + p.addLast(new ProtobufDecoder(PlatonMessage.getDefaultInstance())); + + + p.addLast("eccEncoder", new EccEncoder(LENGTH_FIELD)); + + p.addLast(new ProtobufVarint32LengthFieldPrepender()); + p.addLast(new ProtobufEncoder()); + + p.addLast("nodeServerChannelHandler", new NodeServerChannelHandler()); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/P2pChannelHandler.java b/p2p/src/main/java/org/platon/p2p/P2pChannelHandler.java new file mode 100644 index 0000000..dbab801 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/P2pChannelHandler.java @@ -0,0 +1,143 @@ +package org.platon.p2p; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.platon.common.cache.DelayCache; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.PlatonMessageHelper; +import org.platon.p2p.handler.PlatonMessageHandlerContext; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.proto.session.CreateSession; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.TimeUnit; + + +public abstract class P2pChannelHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LoggerFactory.getLogger(P2pChannelHandler.class); + private static DelayCache txIdCache = new DelayCache<>(); + + protected ByteString remoteNodeId; + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + protected abstract void handleCreateSessionRequest(ChannelHandlerContext ctx, CreateSession createSession); + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + logger.error(""); + } + + + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + + + if(remoteNodeId!=null) { + SessionManager sessionManager = SpringContextUtil.getBean("sessionManager"); + sessionManager.closeSession(remoteNodeId); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, PlatonMessage msg) throws Exception { + logger.debug("Received PlatonMessage:{}", msg); + + Header header = msg.getHeader(); + Any any = msg.getBody().getData(); + + + String messageName = StringUtils.substringAfterLast(any.getTypeUrl(), "/"); + + + String messageSimpleName = StringUtils.substringAfterLast(any.getTypeUrl(), "."); + + + String tx_id = header.getTxId(); + + + + + + List destIdList = header.getDestList(); + + if(PlatonMessageHelper.arrived(destIdList, NodeContext.localNodeId)){ + + String uniqueTxId = StringUtils.trimToEmpty(tx_id) + "_" + messageSimpleName; + if (txIdCache.get(uniqueTxId) == null) { + txIdCache.put(uniqueTxId, true, NodeContext.timeIntervalForDuplicatedMessage, TimeUnit.SECONDS); + } else { + logger.warn("Discarding the duplicated request, tthe unique msg nodeId:={}", uniqueTxId); + return; + } + + + if(StringUtils.equalsIgnoreCase(messageSimpleName, "CreateSession")){ + handleCreateSessionRequest(ctx, any.unpack(CreateSession.class)); + }else{ + + Class messageClz = Class.forName(messageName); + Object message = any.unpack(messageClz); + + Object handler = PlatonMessageHandlerContext.getInstance().getHandler(messageSimpleName); + if (handler != null) { + Method method = PlatonMessageHandlerContext.getInstance().getMethod(messageSimpleName); + + method.invoke(handler, message, HeaderHelper.build(header)); + } + } + + if (CollectionUtils.isNotEmpty(destIdList) && destIdList.size() > 1) { + forwardPlatonMessage(header, any, NodeContext.localNodeId); + } + }else if (header.getTtl() > 0) { + + + + String uniqueTxId = StringUtils.trimToEmpty(tx_id) + "_" + messageName + "_" + destIdList.get(0).getId().toStringUtf8(); + if (txIdCache.get(uniqueTxId) == null) { + txIdCache.put(uniqueTxId, true, NodeContext.timeIntervalForDuplicatedMessage, TimeUnit.SECONDS); + + logger.debug("Just forward request to next node ..."); + + forwardPlatonMessage(header, any, NodeContext.localNodeId); + } else { + logger.warn("Discarding the duplicated passing request, the unique msg nodeId:={}", uniqueTxId); + } + }else{ + logger.debug("Do not forward request to next node, because TTL runs out ..."); + } + } + + + private void forwardPlatonMessage(Header header, Any any, ByteString localNodeId){ + MessageRouter messageRouter = SpringContextUtil.getBean("messageRouter"); + messageRouter.forwardPlatonMessage(header, any, localNodeId); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/attach/LinkController.java b/p2p/src/main/java/org/platon/p2p/attach/LinkController.java new file mode 100644 index 0000000..dbcb309 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/attach/LinkController.java @@ -0,0 +1,113 @@ +package org.platon.p2p.attach; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.plugins.TopologyPlugin; +import org.platon.p2p.proto.attach.AttachMessage; +import org.platon.p2p.proto.attach.AttachRespMessage; +import org.platon.p2p.proto.attach.PingMessage; +import org.platon.p2p.proto.attach.PongMessage; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +/** + * @author Jungle + * @create 2018-05-07 16:10 + */ +@Component("linkController") +public class LinkController { + + private static Logger logger = LoggerFactory.getLogger(LinkController.class); + + @Autowired + MessageRouter messageRouter; + + @Autowired + TopologyPlugin plugin; + + @Autowired + RoutingTable routingTable; + + + public void setPlugin(TopologyPlugin plugin) { + this.plugin = plugin; + } + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + + public CompletableFuture attach(ByteString remoteNodeId) { + logger.trace("remote nodeId:" + remoteNodeId); + NodeID remoteNode = NodeID.newBuilder().setId(remoteNodeId).build(); + + + + return this.attach(remoteNode, routingTable.getLocalNode()); + } + + public CompletableFuture attach(String remoteNodeId) { + logger.trace("remote nodeId:" + remoteNodeId); + NodeID remoteNode = NodeID.newBuilder().setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(remoteNodeId))).build(); + + + + return this.attach(remoteNode, routingTable.getLocalNode()); + } + + + public CompletableFuture attach(NodeID dest, NodeID local) { + logger.debug("Attach dest node:{}, local node:{}", dest.toString(), local.toString()); + final CompletableFuture respFut = new CompletableFuture<>(); + AttachMessage attachMessage = AttachMessage.newBuilder() + .setNodeId(local).build(); + + + + CompletableFuture fut = messageRouter.sendRequest(attachMessage, Collections.singletonList(NodeUtils.toRoutableID(dest)), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + fut.thenAcceptAsync(futMsg -> { + AttachRespMessage attachRespMsg = (AttachRespMessage)futMsg; + plugin.getRoutingTable().add(attachRespMsg.getNodeId()); + respFut.complete(attachRespMsg.getNodeId()); + }, NodeContext.executor).exceptionally(throwable ->{ + respFut.completeExceptionally(throwable); + return null; + }); + + return respFut; + } + + + public CompletableFuture ping(NodeID nodeid) { + logger.trace("ping node nodeId:{}", nodeid); + final CompletableFuture pongFut = new CompletableFuture<>(); + PingMessage pingMsg = PingMessage.newBuilder().setPayloadLength(32).build(); + + CompletableFuture fut = messageRouter.sendRequest(pingMsg, + Collections.singletonList(NodeUtils.toRoutableID(nodeid)), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + fut.thenAcceptAsync(futMsg -> { + pongFut.complete((PongMessage)futMsg); + }, NodeContext.executor).exceptionally(throwable ->{ + logger.warn("ping exception:{}", throwable.getMessage()); + pongFut.completeExceptionally(throwable); + return null; + }); + return pongFut; + } +} diff --git a/p2p/src/main/java/org/platon/p2p/attach/LinkService.java b/p2p/src/main/java/org/platon/p2p/attach/LinkService.java new file mode 100644 index 0000000..2f1a719 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/attach/LinkService.java @@ -0,0 +1,104 @@ +package org.platon.p2p.attach; + +import org.apache.commons.lang3.RandomUtils; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.plugins.TopologyPlugin; +import org.platon.p2p.proto.attach.AttachMessage; +import org.platon.p2p.proto.attach.AttachRespMessage; +import org.platon.p2p.proto.attach.PingMessage; +import org.platon.p2p.proto.attach.PongMessage; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author yangzhou + * @create 2018-04-27 11:55 + */ +@Component("linkService") +public class LinkService { + + private static Logger logger = LoggerFactory.getLogger(LinkService.class); + + @Autowired + private TopologyPlugin plugin; + + @Autowired + private SessionManager sessionManager; + + @Autowired + private MessageRouter messageRouter; + + @Autowired + private RoutingTable routingTable; + + + public void setPlugin(TopologyPlugin plugin) { + this.plugin = plugin; + } + + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + + + + + @PlatonMessageType("AttachMessage") + public void attach(AttachMessage msg, HeaderHelper header){ + logger.trace("Received attach request message:{}", msg.toString()); + + + + plugin.getRoutingTable().add(msg.getNodeId()); + + + + AttachRespMessage attachMessage = AttachRespMessage.newBuilder().setNodeId(routingTable.getLocalNode()).build(); + + + messageRouter.sendResponse(attachMessage, header.txId(), + header.viaToDest(), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + + @PlatonMessageType("AttachRespMessage") + public void attachResp(AttachRespMessage msg, HeaderHelper header){ + logger.trace("Received attach response message:{}", msg); + messageRouter.handleResponse(header.txId(), msg); + } + + + @PlatonMessageType("PingMessage") + public void ping(PingMessage msg, HeaderHelper header){ + logger.trace("Received ping request message:{}", msg.toString()); + PongMessage pongMessage = PongMessage.newBuilder() + .setResponseId(RandomUtils.nextLong()) + .setResponseTime(System.currentTimeMillis()) + .build(); + + messageRouter.sendResponse(pongMessage, header.txId(), + header.viaToDest(), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + + @PlatonMessageType("PingMessage") + public void pingResp(PongMessage msg, HeaderHelper header){ + logger.trace("Received ping response message:{}", msg); + messageRouter.handleResponse(header.txId(), msg); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/Bytes.java b/p2p/src/main/java/org/platon/p2p/common/Bytes.java new file mode 100644 index 0000000..a6ac82b --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/Bytes.java @@ -0,0 +1,100 @@ +package org.platon.p2p.common; + +import com.google.protobuf.ByteString; +import org.platon.p2p.plugins.kademlia.KademliaHelp; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +/** + * @author yangzhou + * @create 2018-08-22 16:45 + */ +public class Bytes implements Comparable { + private final byte[] key; + + + public static class BytesComparator implements Comparator + { + + private final BigInteger key; + + /** + * @param key The NodeId relative to which the distance should be measured. + */ + public BytesComparator(Bytes key) + { + this.key = KademliaHelp.getInt(key); + } + + /** + * Compare two objects which must both be of type Node + * and determine which is closest to the identifier specified in the + * constructor. + * + * @param n1 Node 1 to compare distance from the key + * @param n2 Node 2 to compare distance from the key + */ + @Override + public int compare(Bytes n1, Bytes n2) + { + BigInteger b1 = KademliaHelp.getInt(n1); + BigInteger b2 = KademliaHelp.getInt(n2); + + b1 = b1.xor(key); + b2 = b2.xor(key); + + return b1.abs().compareTo(b2.abs()); + } + } + + public static BytesComparator newBytesComparator(Bytes key) { + return new BytesComparator(key); + } + + + /** + * @param key The NodeId relative to which the distance should be measured. + */ + public Bytes(byte[] key) { + this.key = key; + } + + public static Bytes valueOf(byte[] key){ + return new Bytes(key); + } + public static Bytes valueOf(ByteString key){ + return new Bytes(key.toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Bytes that = (Bytes) o; + return Arrays.equals(key, that.key); + } + + @Override + public int hashCode() { + return Arrays.hashCode(key); + } + + public byte[] getKey() { + return key; + } + + + @Override + public int compareTo(Bytes o) { + return new BigInteger(key).compareTo(new BigInteger(1, o.getKey())); + } + + + +} diff --git a/p2p/src/main/java/org/platon/p2p/common/CodecUtils.java b/p2p/src/main/java/org/platon/p2p/common/CodecUtils.java new file mode 100644 index 0000000..8a41910 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/CodecUtils.java @@ -0,0 +1,13 @@ +package org.platon.p2p.common; + +import com.google.protobuf.ByteString; +import org.platon.common.utils.Numeric; + +public class CodecUtils { + public static String toHexString(ByteString byteString){ + if(byteString==null || byteString.isEmpty()){ + return null; + } + return Numeric.toHexString(byteString.toByteArray()); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/HeaderHelper.java b/p2p/src/main/java/org/platon/p2p/common/HeaderHelper.java new file mode 100644 index 0000000..10bb06a --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/HeaderHelper.java @@ -0,0 +1,57 @@ +package org.platon.p2p.common; + +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Header; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author yangzhou + * @create 2018-08-20 14:44 + */ +public class HeaderHelper { + + + private Header header; + + + public static HeaderHelper build(Header header) { + return new HeaderHelper(header); + } + public HeaderHelper(Header header) { + this.header = header; + } + + public Header getHeader() { + return header; + } + + public List destList() { + return header.getDestList(); + } + + public List viaList() { + return header.getViaList(); + } + + public String txId() { + return header.getTxId(); + } + + public List viaToDest() { + List routableIDList = new ArrayList<>(); + for (NodeID nodeId : header.getViaList()) { + routableIDList.add(NodeUtils.toRoutableID(nodeId)); + } + + return routableIDList; + } + + public NodeID senderId() { + return header.getVia(0); + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/common/KadPluginConfig.java b/p2p/src/main/java/org/platon/p2p/common/KadPluginConfig.java new file mode 100644 index 0000000..1ef1294 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/KadPluginConfig.java @@ -0,0 +1,95 @@ +package org.platon.p2p.common; + +import com.typesafe.config.Config; +import org.platon.common.config.ConfigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

KadPluginConfig.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/2, lvxiaoyi, Initial Version. + */ +public class KadPluginConfig { + private static Logger logger = LoggerFactory.getLogger(KadPluginConfig.class); + + private Config config; + + + public static KadPluginConfig getInstance() { + return new KadPluginConfig(ConfigProperties.getInstance().getConfig().getObject("kad.plugin").toConfig()); + } + + KadPluginConfig(Config config) { + this.config = config; + } + + + + + public int getIdLength() { + return config.getInt("id-length"); + } + + /** + * @return Interval in milliseconds between execution of RestoreOperations. + */ + public long getRestoreInterval() { + return config.getLong("restore-interval"); + } + + + /** + * If no reply received from a node in this period (in milliseconds) + * consider the node unresponsive. + * + * @return The time it takes to consider a node unresponsive + */ + public long getResponseTimeout() { + return config.getLong("response-timeout"); + } + + /** + * @return Maximum number of milliseconds for performing an operation. + */ + public long getOperationTimeout() { + return config.getLong("operation-timeout"); + } + + /** + * @return Maximum number of concurrent messages in transit. + */ + public int maxConcurrentMessagesTransiting() { + return config.getInt("max-concurrent-messages-transiting"); + } + + /** + * @return K-Value used throughout Kademlia + */ + public int getKValue() { + return config.getInt("K-value"); + } + + /** + * @return Size of replacement cache. + */ + public int getReplacementCacheSize() { + return config.getInt("replacement-cache-size"); + } + + /** + * @return # of times a node can be marked as stale before it is actually removed. + */ + public int getStaleTimes() { + return config.getInt("stale-times"); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/LmdbConfig.java b/p2p/src/main/java/org/platon/p2p/common/LmdbConfig.java new file mode 100644 index 0000000..b969050 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/LmdbConfig.java @@ -0,0 +1,53 @@ +package org.platon.p2p.common; + +import com.typesafe.config.Config; +import org.platon.common.config.ConfigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

LmdbConfig.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/11, lvxiaoyi, Initial Version. + */ +public class LmdbConfig { + private static Logger logger = LoggerFactory.getLogger(ReDiRConfig.class); + + private Config config; + + public static LmdbConfig getInstance() { + return new LmdbConfig(ConfigProperties.getInstance().getConfig().getObject("lmdb").toConfig()); + } + + public LmdbConfig(Config config) { + this.config = config; + } + + + public String getLmdbNativeLib() { + return config.getString("lmdbjava-native-lib"); + } + + public String getLmdbName() { + return config.getString("lmdb-name"); + } + + public String getLmdbDataFile() { + return config.getString("lmdb-data-file"); + } + + public int getLmdbMaxReaders() { + return config.getInt("lmdb-max-readers"); + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/common/NodeUtils.java b/p2p/src/main/java/org/platon/p2p/common/NodeUtils.java new file mode 100644 index 0000000..7f8eeb4 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/NodeUtils.java @@ -0,0 +1,118 @@ +package org.platon.p2p.common; + + +import com.google.protobuf.ByteString; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.platon.p2p.plugins.kademlia.KeyComparator; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; + +import java.nio.charset.StandardCharsets; +import java.util.*; + + +/** + *

NodeUtils.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/14, lvxiaoyi, Initial Version. + */ +public class NodeUtils { + + public static byte[] getNodeIdBytes(String nodeId){ + if(nodeId==null){ + throw new IllegalArgumentException(); + } + try { + return Hex.decodeHex(nodeId.toCharArray()); + } catch (DecoderException e) { + e.printStackTrace(); + return null; + } + } + + public static String getNodeIdString(byte[] nodeIdBytes){ + if(nodeIdBytes==null){ + throw new IllegalArgumentException(); + } + return Hex.encodeHexString(nodeIdBytes); + } + + public static String getNodeIdString(ByteString nodeIdBytes){ + if(nodeIdBytes==null){ + throw new IllegalArgumentException(); + } + return Hex.encodeHexString(nodeIdBytes.toByteArray()); + } + + + public static boolean equals(NodeID src, NodeID dest) { + return Arrays.equals(src.getId().toByteArray(), dest.getId().toByteArray()); + } + + public static void main(String[] args){ + String hexString = "74657374206e6f646531";//"test node1" + + byte[] hexBytes = NodeUtils.getNodeIdBytes(hexString); + + String hexString2 = NodeUtils.getNodeIdString(hexBytes); + + System.out.println("hexString:" + hexString2); + + } + + public static NodeID closestNode(RoutableID target, Set peers) { + TreeSet sortedSet = new TreeSet<>(new KeyComparator(toNodeID(target))); + sortedSet.addAll(peers); + return sortedSet.first(); + } + + public static Bytes closestNode(Bytes target, Set peers) { + TreeSet sortedSet = new TreeSet<>(Bytes.newBytesComparator(target)); + sortedSet.addAll(peers); + return sortedSet.first(); + } + + public static NodeID toNodeID(RoutableID routableId) { + return NodeID.newBuilder().setId(routableId.getId()).build(); + } + + public static RoutableID toRoutableID(NodeID nodeId) { + return RoutableID.newBuilder().setId(nodeId.getId()).setType(RoutableID.DestinationType.NODEIDTYPE).build(); + } + + public static RoutableID toRoutableID(ResourceID resourceId) { + return RoutableID.newBuilder().setId(resourceId.getId()).setType(RoutableID.DestinationType.RESOURCEIDTYPE).build(); + } + + public static RoutableID toRoutableID(ByteString byteString) { + return RoutableID.newBuilder().setId(byteString).setType(RoutableID.DestinationType.RESOURCEIDTYPE).build(); + } + + public static RoutableID toRoutableID(Bytes bytes, RoutableID.DestinationType type) { + return RoutableID.newBuilder().setId(ByteString.copyFrom(bytes.getKey())).setType(type).build(); + } + + public static List nodeIdListToRoutableIdList(List nodeIdList){ + List routableIdList = new ArrayList<>(); + for (NodeID nodeId : nodeIdList) { + routableIdList.add(RoutableID.newBuilder().setId(nodeId.getId()).setType(RoutableID.DestinationType.NODEIDTYPE).build()); + } + return routableIdList; + } + + public static String base64Endpoint(String endpoint){ + return Base64.encodeBase64String(endpoint.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/PeerConfig.java b/p2p/src/main/java/org/platon/p2p/common/PeerConfig.java new file mode 100644 index 0000000..3e4d9c8 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/PeerConfig.java @@ -0,0 +1,46 @@ +package org.platon.p2p.common; + +import com.typesafe.config.Config; +import org.platon.common.config.ConfigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class PeerConfig { + private static Logger logger = LoggerFactory.getLogger(PeerConfig.class); + + private Config config; + + public static PeerConfig getInstance() { + return new PeerConfig(ConfigProperties.getInstance().getConfig().getObject("peer").toConfig()); + } + + public PeerConfig(Config config) { + this.config = config; + } + + + public int getPort() { + return config.getInt("listen.port"); + } + + public List getActiveNodeConfig() { + return (List) config.getConfigList("active.list"); + } + + public int getCreateSessionTimeout() { + return config.getInt("create.session.timeout"); + } + public int getMessageResponseTimeout() { + return config.getInt("message.response.timeout"); + } + public int getPeerConnectTimeout() { + return config.getInt("peer.connect.timeout"); + } + + public int getTimeIntervalForDuplicatedMessage() { + return config.getInt("time.interval.for.duplicated.message"); + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/common/PlatonMessageHelper.java b/p2p/src/main/java/org/platon/p2p/common/PlatonMessageHelper.java new file mode 100644 index 0000000..a302352 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/PlatonMessageHelper.java @@ -0,0 +1,214 @@ +package org.platon.p2p.common; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.crypto.ECKey; +import org.platon.crypto.HashUtil; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.NodeContext; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Body; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.proto.session.CreateSession; + +import java.util.List; +import java.util.UUID; + +/** + *

PlatonMessageHelper.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/22, lvxiaoyi, Initial Version. + */ +public class PlatonMessageHelper { + public static boolean arrived(List destIdList, ByteString localNodeId){ + return CollectionUtils.isEmpty(destIdList) || existInRoutables(destIdList, localNodeId); + } + + /*public static PlatonMessageHandler getHandler(String messageName){ + return (PlatonMessageHandler)SpringContextUtil.getBean(messageName + "Handler"); + }*/ + +/* public static int decreaseTtl(Header header){ + int counter = header.getTtl(); + if(counter>1){ + Header.newBuilder(header).setTtl(counter--); + return counter; + } + return 0; + }*/ + + /*public static void removeDestNode(Header header, ByteString localNodeId) { + RoutableID routableId = RoutableID.newBuilder().setType(RoutableID.DestinationType.NODEIDTYPE).setId(localNodeId).build(); + header.getDestList().remove(routableId); + }*/ + + +/* + public static boolean viaed(Header header, ByteString localNodeId){ + return viaed(header.getViaList(), localNodeId); + } +*/ + + + public static boolean viaed(List viaIDList, ByteString localNodeId){ + return existInNodes(viaIDList, localNodeId); + } + + public static Header renewHeader(Header header, ByteString localNodeId){ + NodeID localNode = NodeID.newBuilder().setId(localNodeId).build(); + Header.Builder builder = Header.newBuilder(header); + + int idx = header.getViaList().indexOf(localNode); + if(idx == -1){ + builder.addVia(localNode); + } + + RoutableID routableId = RoutableID.newBuilder().setType(RoutableID.DestinationType.NODEIDTYPE).setId(localNodeId).build(); + idx = header.getDestList().indexOf(routableId); + if(idx >= 0){ + builder.removeDest(idx); + } + + int ttl = header.getTtl(); + if(ttl>0){ + ttl--; + builder.setTtl(ttl); + } + + return builder.build(); + } + + public static Header addVia(Header header, ByteString localNodeId){ + + NodeID localNode = NodeID.newBuilder().setId(localNodeId).build(); + + int idx = header.getViaList().indexOf(localNode); + if(idx == -1){ + return Header.newBuilder(header).addVia(localNode).build(); + } + return header; + } + + public static String getMessageName(Any any){ + return StringUtils.substringAfterLast(any.getTypeUrl(), "."); + } + + + public static PlatonMessage createCreateSession(){ + byte[] messageHash = HashUtil.sha3(NodeContext.localNodeId.toByteArray()); + ECKey ecKey = ECKey.fromPrivate(NodeContext.privateKey); + + CreateSession createSession = CreateSession.newBuilder() + .setClientNodeId(NodeContext.localNodeId) + .setEndpoint(NodeContext.getEndpoint()) + .setMessageHash(ByteString.copyFrom(messageHash)) + .setSignature(ByteString.copyFrom(WalletUtil.sign(messageHash, ecKey))) + .build(); + + Body body = Body.newBuilder().setData(Any.pack(createSession)).build(); + return PlatonMessage.newBuilder().setHeader(PlatonMessageHelper.createHeader()).setBody(body).build(); + } + + public static Header createHeader(){ + NodeID viaId = NodeID.newBuilder().setId(NodeContext.localNodeId).build(); + return Header.newBuilder().setTtl(1).setTxId(UUID.randomUUID().toString()).addVia(viaId).build(); + } + + public static Header createHeader(ByteString remoteNodeId){ + RoutableID destId = RoutableID.newBuilder().setId(remoteNodeId).setType(RoutableID.DestinationType.NODEIDTYPE).build(); + + NodeID viaId = NodeID.newBuilder().setId(NodeContext.localNodeId).build(); + + return Header.newBuilder().setTtl(1).addDest(destId).setTxId(UUID.randomUUID().toString()).addVia(viaId).build(); + } + + + public static PlatonMessage createPlatonMessage(ByteString remoteNodeId, com.google.protobuf.Message message){ + Body body = Body.newBuilder().setData(Any.pack(message)).build(); + return PlatonMessage.newBuilder().setHeader(createHeader(remoteNodeId)).setBody(body).build(); + } + + + private static boolean existInNodes(List nodeIdList, ByteString localNodeId ){ + if(nodeIdList!=null && nodeIdList.size()>0){ + for(NodeID nodeId : nodeIdList){ + if (localNodeId.equals(nodeId.getId())) { + return true; + } + } + } + return false; + } + + private static boolean existInRoutables(List idList, ByteString localNodeId){ + if(idList!=null && idList.size()>0){ + for(RoutableID routableID : idList){ + if(routableID.getType()==RoutableID.DestinationType.NODEIDTYPE) { + if (localNodeId.equals(routableID.getId())) { + return true; + } + }else if(routableID.getType()==RoutableID.DestinationType.RESOURCEIDTYPE){ + RoutingTable routingTable = SpringContextUtil.getBean("routingTable"); + List nodeIDList = routingTable.getNeighbors(routableID, 1); + if(CollectionUtils.isNotEmpty(nodeIDList) && localNodeId.equals(nodeIDList.get(0).getId())) { + return true; + } + } + } + } + return false; + } + + + public static void main(String[] args){ + + NodeID localNode = NodeID.newBuilder().setId(ByteString.copyFrom(Numeric.hexStringToByteArray("0x9f6d816d91405c36d3c408f2bcdae97f5e5df182"))).build(); + NodeID localNode1 = NodeID.newBuilder().setId(ByteString.copyFrom(Numeric.hexStringToByteArray("0x0cfa5fcf00b1f552c070c398bcfc002c8798208a"))).build(); + NodeID localNode2 = NodeID.newBuilder().setId(ByteString.copyFrom(Numeric.hexStringToByteArray("0x282a993adaed9b6dff554e0992d02211604605bf"))).build(); + + RoutableID routableID1 = RoutableID.newBuilder().setId(ByteString.copyFrom(Numeric.hexStringToByteArray("0x0cfa5fcf00b1f552c070c398bcfc002c8798208a"))) + .setType(RoutableID.DestinationType.NODEIDTYPE).build(); + + RoutableID routableID2 = RoutableID.newBuilder().setId(ByteString.copyFrom(Numeric.hexStringToByteArray("0x282a993adaed9b6dff554e0992d02211604605bf"))) + .setType(RoutableID.DestinationType.NODEIDTYPE).build(); + + + Header header = Header.newBuilder().setTtl(10).setTxId("txId").setVersion("1.0").addDest(routableID1).addDest(routableID2).addVia(localNode).addVia(localNode1).build(); + /*System.out.println(header.getViaList().size()); + System.out.println(header.toString()); + + header = Header.newBuilder(header).addVia(localNode2).build(); + System.out.println(header.getViaList().size()); + System.out.println(header.toString()); + + + int idx = header.getViaList().indexOf(localNode2); + if(idx>=0) { + header = Header.newBuilder(header).removeVia(idx).build(); + } + System.out.println(header.getViaList().size()); + System.out.println(header.toString());*/ + + + header = PlatonMessageHelper.renewHeader(header, ByteString.copyFrom(Numeric.hexStringToByteArray("0x282a993adaed9b6dff554e0992d02211604605bf"))); + + System.out.println(header.toString()); + + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/ProtoBufHelper.java b/p2p/src/main/java/org/platon/p2p/common/ProtoBufHelper.java new file mode 100644 index 0000000..b3a0d5e --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/ProtoBufHelper.java @@ -0,0 +1,50 @@ +package org.platon.p2p.common; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Internal; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; + +/** + * @author yangzhou + * @create 2018-08-22 14:42 + */ +public class ProtoBufHelper { + public static String getTypeNameFromTypeUrl( + java.lang.String typeUrl) { + int pos = typeUrl.lastIndexOf('/'); + return pos == -1 ? "" : typeUrl.substring(pos + 1); + } + + public static String getFullName(Class clazz) { + return (Internal.getDefaultInstance(clazz)).getDescriptorForType().getFullName(); + } + + + + public static ByteString encodeIceData(Message v) { + return v.toByteString(); + } + + + + public static T decodeIceData(ByteString buf, com.google.protobuf.Parser parser) { + T data = null; + try { + data = parser.parseFrom(buf); + } catch (InvalidProtocolBufferException e) { + data = null; + } + return data; + } + + public static T decodeIceData(byte[] buf, com.google.protobuf.Parser parser) { + T data = null; + try { + data = parser.parseFrom(buf); + } catch (InvalidProtocolBufferException e) { + data = null; + } + return data; + } +} diff --git a/p2p/src/main/java/org/platon/p2p/common/ReDiRConfig.java b/p2p/src/main/java/org/platon/p2p/common/ReDiRConfig.java new file mode 100644 index 0000000..7d01a91 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/common/ReDiRConfig.java @@ -0,0 +1,66 @@ +package org.platon.p2p.common; + +import com.typesafe.config.Config; +import org.platon.common.config.ConfigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; + +/** + *

ReDiRConfig.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/2, lvxiaoyi, Initial Version. + */ +public class ReDiRConfig { + + private static Logger logger = LoggerFactory.getLogger(ReDiRConfig.class); + + private Config config; + + public static ReDiRConfig getInstance() { + return new ReDiRConfig(ConfigProperties.getInstance().getConfig().getObject("redir").toConfig()); + } + + public ReDiRConfig(Config config) { + this.config = config; + } + + + public int getBranchingFactor(String namespace) { + return config.getInt(namespace + ".branching-factor"); + } + + public int getLevel(String namespace) { + return config.getInt(namespace + ".level"); + } + + + public BigInteger getLowestKey(String namespace) { + return new BigInteger(config.getString(namespace + ".lowest-key")); + } + + + public BigInteger getHighestKey(String namespace) { + return new BigInteger(config.getString(namespace + ".highest-key")); + } + + + public int getStartLevel(String namespace) { + return config.getInt(namespace + ".start-level"); + } + + public String getAlgorithm(String namespace) { + return config.getString(namespace + ".algorithm"); + } + +} \ No newline at end of file diff --git a/p2p/src/main/java/org/platon/p2p/db/DB.java b/p2p/src/main/java/org/platon/p2p/db/DB.java new file mode 100644 index 0000000..36aaae6 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/db/DB.java @@ -0,0 +1,32 @@ +package org.platon.p2p.db; + +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; + + + +public interface DB { + + final static int BigIntegerLength = 20; + void set(final byte[] key, final byte[] value) throws DBException; + byte[] get(final byte[] key) throws DBException; + void del(final byte[] key) throws DBException; + + void hset(final byte[] name, final byte[] key, final byte[] value) throws DBException; + byte[] hget(final byte[] name, final byte[] key) throws DBException; + List> hgetAll(final byte[] name) throws DBException; + void hdel(final byte[] name, final byte[] key) throws DBException; + long hsize(final byte[] name)throws DBException; + + + + + void zset(final byte[] name, final byte[] key, final byte[] score)throws DBException; + void zdel(final byte[] name, final byte[] key) throws DBException; + byte[] zget(final byte[] name, final byte[] key) throws DBException; + long zrank(final byte[] name, final byte[] key) throws DBException; + long zsize(final byte[] name) throws DBException; + + +} \ No newline at end of file diff --git a/p2p/src/main/java/org/platon/p2p/db/DBException.java b/p2p/src/main/java/org/platon/p2p/db/DBException.java new file mode 100644 index 0000000..9ca9643 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/db/DBException.java @@ -0,0 +1,11 @@ +package org.platon.p2p.db; + +/** + * @author Jungle + * @create 2018-04-28 16:46 + */ +public class DBException extends Exception { + public DBException(final String msg) { + super(msg, null); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/db/DBMemoryImp.java b/p2p/src/main/java/org/platon/p2p/db/DBMemoryImp.java new file mode 100644 index 0000000..9f8fe3b --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/db/DBMemoryImp.java @@ -0,0 +1,240 @@ +package org.platon.p2p.db; + +import org.apache.commons.collections.map.HashedMap; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.*; + + +@Component("db") +public class DBMemoryImp implements DB{ + Map storage; + + Map> setStorage; + + Map>> zsetScoreStorage; + Map> zsetStorage; + + + + public static class ByteArrayWrapper { + private final byte[] data; + + public ByteArrayWrapper(byte[] data) + { + if (data == null) + { + throw new NullPointerException(); + } + this.data = data; + } + + public static ByteArrayWrapper valueOf(byte[] data) { + return new ByteArrayWrapper(data); + } + byte[] toBytes() { + return data; + } + @Override + public boolean equals(Object other) + { + if (!(other instanceof ByteArrayWrapper)) + { + return false; + } + return Arrays.equals(data, ((ByteArrayWrapper)other).data); + } + + @Override + public int hashCode() + { + return Arrays.hashCode(data); + } + } + public DBMemoryImp() { + storage = new HashMap<>(); + + setStorage = new HashMap<>(); + + zsetStorage = new HashedMap(); + zsetScoreStorage = new HashedMap(); + } + + public synchronized void set(final byte[] key, final byte[] value) throws DBException{ + + storage.put(ByteArrayWrapper.valueOf(key), ByteArrayWrapper.valueOf(value)); + } + + public synchronized byte[] get(final byte[] key) throws DBException { + ByteArrayWrapper value = storage.get(ByteArrayWrapper.valueOf(key)); + return value == null ? null : value.toBytes(); + } + + public synchronized void del(final byte[] key) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + + if (storage.containsKey(wrapperKey)) { + storage.remove(wrapperKey); + } + + } + + public synchronized void hset(final byte[] name, final byte[] key, final byte[] value) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperValue = ByteArrayWrapper.valueOf(value); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + + if (!setStorage.containsKey(wrapperName)) { + Map data = new HashMap<>(); + data.put(wrapperKey, wrapperValue); + setStorage.put(wrapperName, data); + } + setStorage.get(wrapperName).put(wrapperKey, wrapperValue); + + } + public synchronized byte[] hget(final byte[] name, final byte[] key) throws DBException{ + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + if (!setStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + + ByteArrayWrapper value = setStorage.get(wrapperName).get(wrapperKey); + return value == null ? null : value.toBytes(); + } + + public synchronized List> hgetAll(final byte[] name) throws DBException { + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + if (!setStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + List> res = new ArrayList<>(); + for (Map.Entry n : setStorage.get(wrapperName).entrySet()) { + res.add(Pair.of(n.getKey().toBytes(), n.getValue().toBytes())); + } + return res; + } + + public synchronized void hdel(final byte[] name, final byte[] key) throws DBException{ + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + if (!setStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + if (setStorage.containsKey(wrapperName)) { + setStorage.get(wrapperName).remove(wrapperKey); + + } + } + + public synchronized long hsize(final byte[] name) throws DBException { + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + if (!setStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + return setStorage.get(wrapperName).size(); + } + + public synchronized void zset(final byte[] name, final byte[] key, final byte[] score) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + BigInteger bigIntegerScore = new BigInteger(score); + + + + if (!zsetScoreStorage.containsKey(wrapperName)) { + + Map keyMap = new HashMap<>(); + Map> scoreMap = new TreeMap<>(); + List keyList = new ArrayList<>(); + keyMap.put(wrapperKey, bigIntegerScore); + keyList.add(wrapperKey); + scoreMap.put(bigIntegerScore, keyList); + zsetScoreStorage.put(wrapperName, scoreMap); + zsetStorage.put(wrapperName, keyMap); + } else { + if (zsetStorage.get(wrapperName).containsKey(wrapperKey)) { + + BigInteger old = zsetStorage.get(wrapperName).get(wrapperKey); + zsetScoreStorage.get(wrapperName).get(old).remove(wrapperKey); + } else { + zsetStorage.get(wrapperName).put(wrapperKey, bigIntegerScore); + } + + if (!zsetScoreStorage.get(wrapperName).containsKey(bigIntegerScore)) { + List keyList = new ArrayList<>(); + keyList.add(wrapperKey); + zsetScoreStorage.get(wrapperName).put(bigIntegerScore, keyList); + } else { + zsetScoreStorage.get(wrapperName).get(bigIntegerScore).add(wrapperKey); + } + } + } + + public synchronized void zdel(final byte[] name, final byte[] key) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + + if (!zsetStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + + if (zsetStorage.get(wrapperName).containsKey(wrapperKey)) { + BigInteger old = zsetStorage.get(wrapperName).get(wrapperKey); + zsetScoreStorage.get(wrapperName).get(old).remove(wrapperKey); + zsetStorage.get(wrapperName).remove(wrapperKey); + } + } + + public synchronized byte[] zget(final byte[] name, final byte[] key) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + + if (!zsetStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + + if (zsetStorage.get(wrapperName).containsKey(wrapperKey)) { + return zsetStorage.get(wrapperName).get(wrapperKey).toByteArray(); + } + return null; + } + + public synchronized long zrank(final byte[] name, final byte[] key) throws DBException { + ByteArrayWrapper wrapperKey = ByteArrayWrapper.valueOf(key); + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + + if (!zsetStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + + int pos = 0; + if (zsetStorage.get(wrapperName).containsKey(wrapperKey)) { + BigInteger old = zsetStorage.get(wrapperName).get(wrapperKey); + + Iterator iter = zsetScoreStorage.get(wrapperName).keySet().iterator(); + while (iter.hasNext()) { + BigInteger n = iter.next(); + if (old != n) { + pos += 1; + } else { + break; + } + + } + } + return pos; + } + + public synchronized long zsize(final byte[] name) throws DBException { + ByteArrayWrapper wrapperName = ByteArrayWrapper.valueOf(name); + + if (!zsetStorage.containsKey(wrapperName)){ + throw new DBException("key is not in Set"); + } + return zsetStorage.get(wrapperName).size(); + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/db/LmdbImp.java b/p2p/src/main/java/org/platon/p2p/db/LmdbImp.java new file mode 100644 index 0000000..2db3afb --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/db/LmdbImp.java @@ -0,0 +1,586 @@ +package org.platon.p2p.db; + +import org.apache.commons.lang3.tuple.Pair; +import org.lmdbjava.*; +import org.platon.p2p.common.LmdbConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Properties; + +import static java.nio.ByteBuffer.allocate; +import static java.nio.ByteBuffer.allocateDirect; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.KeyRange.closed; + + + + +public class LmdbImp implements DB{ + private static Logger logger = LoggerFactory.getLogger(LmdbImp.class); + + + private Dbi dbi; + private Env env; + enum DataType { + HASH((byte)1), + HASHSIZE((byte)2), + ZSET((byte)3), + ZSCORE((byte)4), + ZSIZE((byte)5); + + private final byte code; + + private DataType(byte code) { + this.code = code; + } + + public static DataType valueOf(byte code) { + for (DataType mc : EnumSet.allOf(DataType.class)) + if (mc.code == code) + return mc; + return null; + } + + public byte getCode() { + return code; + } + } + + private static class NameKeyValue { + ByteBuffer name; + ByteBuffer key; + ByteBuffer value; + } + + public LmdbImp(){ + + + Properties pro = System.getProperties(); + logger.trace("Staring LMDDB"); + + String confPath = LmdbConfig.getInstance().getLmdbNativeLib(); + String workDir = System.getProperty("user.dir"); + String filePath = workDir.concat(File.separator).concat(confPath); + System.out.println("LD_PATH:" + filePath); + + final File nativeLib = new File(filePath); + boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("win"); + if (nativeLib.isFile() && nativeLib.exists()){ + pro.setProperty("lmdbjava.native.lib", filePath); + } else { + if (!nativeLib.exists()) { + if (isWindows) { + pro.setProperty("lmdbjava.native.lib", LmdbImp.class.getClass().getResource("/liblmdb.dll").getPath()); + } else { + pro.setProperty("lmdbjava.native.lib", LmdbImp.class.getClass().getResource("/liblmdb.so").getPath()); + } + } else { + if (isWindows) { + pro.setProperty("lmdbjava.native.lib", filePath + "\\liblmdb.dll"); + } else { + pro.setProperty("lmdbjava.native.lib", filePath + "/liblmdb.so"); + } + } + } + + String dbConfPath = LmdbConfig.getInstance().getLmdbDataFile(); + String dbFilePath = workDir.concat(File.separator).concat(dbConfPath); + final File path = new File(dbFilePath); + if(!path.exists()){ + try { + path.getParentFile().mkdirs(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + env = create(ByteBufferProxy.PROXY_OPTIMAL).setMaxReaders(500).open(path, MDB_NOSUBDIR ); + EnvInfo info = env.info(); + + dbi= env.openDbi(LmdbConfig.getInstance().getLmdbName(), DbiFlags.MDB_CREATE); + + + } + + @Override + public void set(final byte[] key, final byte[] value) throws DBException { + dbi.put(slice(key), slice(value)); + } + + @Override + public byte[] get(final byte[] key) throws DBException { + try(Txn txn = env.txnRead()) { + ByteBuffer buffer = dbi.get(txn, slice(key)); + if (buffer != null) { + byte[] value = new byte[buffer.remaining()]; + buffer.get(value); + return value; + } + } + return null; + } + + @Override + public void del(final byte[] key) throws DBException { + dbi.delete(slice(key)); + } + + @Override + public void hset(final byte[] name, final byte[] key, final byte[] value) throws DBException{ + try(Txn txn = env.txnWrite()) { + ByteBuffer hkey = encodeHashNameKey(name, key); + dbi.put(txn, hkey, slice(value)); + increSizeInteral(txn, encodeHashSizeKey(name)); + txn.commit(); + } + } + + @Override + public byte[] hget(final byte[] name, final byte[] key) throws DBException{ + ByteBuffer hkey = encodeHashNameKey(name, key); + try(Txn txn = env.txnRead()) { + ByteBuffer buffer = dbi.get(txn, hkey); + if (buffer != null) { + byte[] value = new byte[buffer.remaining()]; + buffer.get(value); + return value; + } + } + + return null; + } + + @Override + public List> hgetAll(final byte[] name) throws DBException { + List> res = new ArrayList<>(); + + KeyRange keyRange = hashAtLeast(name); + + try(Txn txn = env.txnRead()) { + final CursorIterator iter = dbi.iterate(txn, keyRange); + + + for (CursorIterator.KeyVal next : iter.iterable()){ + byte[] value = new byte[next.val().remaining()]; + NameKeyValue n = decodeHashNameKey(next.key()); + + byte[] key = new byte[n.key.remaining()]; + n.key.get(key); + next.val().get(value); + res.add(Pair.of(key, value)); + } + } + + return res; + } + + @Override + public void hdel(final byte[] name, final byte[] key) throws DBException { + try(Txn txn = env.txnWrite()) { + if (dbi.delete(txn, encodeHashNameKey(name, key))) { + decrSizeInteral(txn, encodeHashSizeKey(name)); + } + txn.commit(); + } + } + + @Override + public long hsize(final byte[] name) throws DBException { + try(Txn txn = env.txnRead()) { + ByteBuffer size = dbi.get(txn, encodeHashSizeKey(name)); + if (size == null) { + throw new DBException("get hash set size failed"); + } + + return size.getLong(); + } + } + + @Override + public void zset(final byte[] name, final byte[] key, final byte[] score) throws DBException { + try(Txn txn = env.txnWrite()) { + ByteBuffer scoreKey = encodeZScoreKey(name, key, score); + ByteBuffer setKey = encodeZSetKey(name, key); + boolean needIncr = false; + + try(Txn txnRead = env.txn(txn)) { + ByteBuffer oldScore = dbi.get(txnRead, setKey); + if (oldScore != null) { + if (oldScore.compareTo(slice(score)) == 0){ + txnRead.close(); + return; + } + + dbi.delete(txn, encodeZScoreKey(name, key, oldScore.asCharBuffer().toString().getBytes())); + } else { + needIncr = true; + } + } + if (needIncr) { + increSizeInteral(txn, encodeZSizeKey(name)); + } + dbi.put(txn, scoreKey, slice("22".getBytes())); + dbi.put(txn, setKey, slice(score)); + txn.commit(); + } + } + + + @Override + public byte[] zget(final byte[] name, final byte[] key) throws DBException { + try(Txn txn = env.txnRead()) { + ByteBuffer setKey = encodeZSetKey(name, key); + ByteBuffer buf = dbi.get(txn, setKey); + if (buf != null) { + byte[] value = new byte[buf.remaining()]; + buf.get(value); + return value; + } + } + return null; + } + + + private void increSizeInteral(Txn txn, ByteBuffer name) throws DBException { + long size = 1; + ByteBuffer oldSize = dbi.get(txn, name); + if (oldSize != null && oldSize.hasRemaining()) { + size = oldSize.getLong() + 1; + } + + ByteBuffer newSize = allocateDirect(8); + newSize.putLong(size); + newSize.rewind(); + dbi.put(txn, name, newSize); + + } + + + private void decrSizeInteral(Txn txn,ByteBuffer name) throws DBException { + + ByteBuffer oldSize = dbi.get(txn, name); + long size = 0; + if (oldSize.hasRemaining() && oldSize.getLong() > 0) { + size = ((ByteBuffer)oldSize.rewind()).getLong() - 1; + } else { + throw new DBException("decr size overflow"); + } + + ByteBuffer newSize = allocateDirect(8); + newSize.putLong(size).rewind(); + dbi.put(txn, name, newSize); + } + + @Override + public void zdel(final byte[] name, final byte[] key) throws DBException { + ByteBuffer delKey = encodeZSetKey(name, key); + try(Txn txn = env.txnWrite()) { + + ByteBuffer oldScore = dbi.get(txn, delKey); + if (oldScore == null) { + return; + } + + + dbi.delete(txn, delKey); + dbi.delete(txn, encodeZScoreKey(name, key, oldScore.asCharBuffer().toString().getBytes())); + decrSizeInteral(txn, encodeZSizeKey(name)); + txn.commit(); + } + } + + @Override + public long zrank(final byte[] name, final byte[] key) throws DBException { + long rank = 0; + ByteBuffer keyBuffer = allocate(key.length); + keyBuffer.put(key).rewind(); + + try(Txn txn = env.txnRead()) { + KeyRange keyRange = zScoreAtLeast(name); + + final CursorIterator iter = dbi.iterate(txn, keyRange); + + while (iter.hasNext()) { + CursorIterator.KeyVal next = iter.next(); + NameKeyValue res = decodeZScoreKey(next.key()); + if (res.key.compareTo(keyBuffer) == 0) { + return rank; + } else { + rank += 1; + } + } + } + throw new DBException("zrank failed"); + } + + @Override + public long zsize(final byte[] name) throws DBException { + try(Txn txn = env.txnRead()) { + ByteBuffer size = dbi.get(txn, encodeZSizeKey(name)); + if (size == null) { + throw new DBException("get zset size failed"); + } + return size.getLong(); + } + } + + static private ByteBuffer slice(final byte[] value) { + final ByteBuffer bb = allocateDirect(value.length); + bb.put(value).flip(); + return bb; + } + + + public static KeyRange hashAtLeast(byte[] name) { + ByteBuffer startBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length); + + ByteBuffer stopBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length + 1); + + startBuffer.put(DataType.HASH.getCode()); + startBuffer.putShort((short) name.length); + startBuffer.put(name); + startBuffer.rewind(); + stopBuffer.put(DataType.HASH.getCode()); + stopBuffer.putShort((short) name.length); + stopBuffer.put(name); + stopBuffer.put((byte) 0xFF); + stopBuffer.rewind(); + return closed(startBuffer, stopBuffer); + } + + + static private ByteBuffer encodeHashNameKey(byte[] name, byte[] key) throws DBException { + if (name.length == 0 || key.length == 0) { + throw new DBException("hash name key encode failed"); + } + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length + key.length); + + + byteBuffer.put(DataType.HASH.getCode()); + byteBuffer.putShort((short) name.length); + byteBuffer.put(name); + byteBuffer.put(key); + byteBuffer.rewind(); + return byteBuffer; + } + + + static private NameKeyValue decodeHashNameKey(ByteBuffer buf) throws DBException { + NameKeyValue res = new LmdbImp.NameKeyValue(); + DataType type = DataType.valueOf(buf.get()); + + if (type == DataType.HASH) { + short nameLen = buf.getShort(); + byte[] byteName = new byte[nameLen]; + buf.get(byteName, 0, nameLen); + byte[] byteKey = new byte[buf.remaining()]; + buf.get(byteKey); + + res.name = allocate(byteName.length); + res.key = allocate(byteKey.length); + res.name.put(byteName).rewind(); + res.key.put(byteKey).rewind(); + } else { + throw new DBException("hash name key decode failed"); + } + return res; + } + static private ByteBuffer encodeHashSizeKey(byte[] name) throws DBException { + if (name.length == 0) { + throw new DBException("hash set key encode failed"); + } + + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length); + + + byteBuffer.put(DataType.HASHSIZE.getCode()); + byteBuffer.putShort((short) name.length); + byteBuffer.put(name); + byteBuffer.rewind(); + + return byteBuffer; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + static private ByteBuffer encodeZScoreKey(byte[] name, byte[] key, byte[] score) throws DBException { + if (name.length == 0 || key.length == 0 || score.length == 0 || score.length > BigIntegerLength) { + throw new DBException("zscore key encode failed"); + } + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length + 21 + key.length); + + BigInteger scoreBigInt = new BigInteger(score); + + byteBuffer.put(DataType.ZSCORE.getCode()); + byteBuffer.putShort((short) name.length); + byteBuffer.put(name); + + + if (scoreBigInt.signum() < 0) { + byteBuffer.put((byte) '-'); + } else { + byteBuffer.put((byte) '='); + } + + if (score.length < BigIntegerLength) { + byteBuffer.put(new byte[BigIntegerLength - score.length]); + } + + byteBuffer.put(score); + byteBuffer.put(key); + byteBuffer.rewind(); + return byteBuffer; + } + + static private NameKeyValue decodeZScoreKey(ByteBuffer buf) throws DBException { + NameKeyValue res = new NameKeyValue(); + DataType type = DataType.valueOf(buf.get(0)); + if (type == DataType.ZSCORE) { + buf.get(); + short nameLen = buf.getShort(); + byte[] byteName = new byte[nameLen]; + buf.get(byteName, 0, nameLen); + + byte signum = buf.get(); + + byte[] byteScore = new byte[BigIntegerLength]; + buf.get(byteScore); + + byte[] byteKey = new byte[buf.remaining()]; + buf.get(byteKey); + + res.name = allocate(byteName.length); + res.key = allocate(byteKey.length); + res.value = allocate(byteScore.length); + res.name.put(byteName).rewind(); + res.key.put(byteKey).rewind(); + res.value.put(byteScore).rewind(); + } else { + throw new DBException("zscore key decode failed"); + } + return res; + } + + static private KeyRange zScoreAtLeast(byte[] name) { + ByteBuffer startBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length); + ByteBuffer stopBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length+1); + startBuffer.put(DataType.ZSCORE.getCode()); + startBuffer.putShort((short) name.length); + startBuffer.put(name); + startBuffer.rewind(); + + stopBuffer.put(DataType.ZSCORE.getCode()); + stopBuffer.putShort((short) name.length); + stopBuffer.put(name); + stopBuffer.put((byte)0xFF); + stopBuffer.rewind(); + return closed(startBuffer, stopBuffer); + } + + static private ByteBuffer encodeZSetKey(byte[] name, byte[] key) throws DBException { + if (name.length == 0 || key.length == 0) { + throw new DBException("zset key encode failed"); + } + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length + key.length); + + + byteBuffer.put(DataType.ZSET.getCode()); + byteBuffer.putShort((short) name.length); + byteBuffer.put(name); + byteBuffer.put(key); + byteBuffer.rewind(); + + return byteBuffer; + } + + static private NameKeyValue decodeZSetKey(ByteBuffer buf) throws DBException { + NameKeyValue res = new NameKeyValue(); + DataType type = DataType.valueOf(buf.get(0)); + if (type == DataType.ZSET) { + short nameLen = buf.getShort(); + byte[] byteName = new byte[nameLen]; + buf.get(byteName, 0, nameLen); + int keyLen = buf.getInt(); + byte[] byteKey = new byte[keyLen]; + buf.get(byteKey); + + res.name = allocate(byteName.length); + res.key = allocate(byteKey.length); + res.name.put(byteName).rewind(); + res.key.put(byteKey).rewind(); + } else { + throw new DBException("zset key encode failed"); + } + return res; + } + + static private ByteBuffer encodeZSizeKey(byte[] name) throws DBException { + if (name.length == 0) { + throw new DBException("zsize key encode failed"); + } + + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 + 2 + name.length); + + + byteBuffer.put(DataType.ZSIZE.getCode()); + byteBuffer.putShort((short) name.length); + byteBuffer.put(name); + byteBuffer.rewind(); + + return byteBuffer; + } + + static private void decodeZSizeKey(ByteBuffer buf, ByteBuffer name) throws DBException { + DataType type = DataType.valueOf(buf.get(0)); + if (type == DataType.ZSIZE) { + short nameLen = buf.getShort(); + if (nameLen == 0) { + throw new DBException("zsize name too short"); + } + byte[] byteName = new byte[nameLen]; + buf.get(byteName, 0, nameLen); + name.put(byteName); + } else { + throw new DBException("zsize key encode failed"); + } + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandler.java b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandler.java new file mode 100644 index 0000000..c5cc31f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandler.java @@ -0,0 +1,27 @@ +package org.platon.p2p.handler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

PlatonMessageHandler.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/30, lvxiaoyi, Initial Version. + */ + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface PlatonMessageHandler { + String name(); +} diff --git a/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandlerContext.java b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandlerContext.java new file mode 100644 index 0000000..e1f3da7 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageHandlerContext.java @@ -0,0 +1,126 @@ +package org.platon.p2p.handler; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; +import org.platon.common.utils.ByteUtil; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.attach.LinkService; +import org.platon.p2p.proto.attach.AttachMessage; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Body; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.reflections.Reflections; +import org.reflections.scanners.MethodAnnotationsScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author lvxy + * @version 0.0.1 + * @date 2018/8/30 17:16 + */ +public class PlatonMessageHandlerContext { + + private static final Logger logger = LoggerFactory.getLogger(PlatonMessageHandlerContext.class); + + private static Map beanMap = new HashMap<>(); + private static Map methodMap = new HashMap<>(); + + public Object getHandler(String messageName){ + return SpringContextUtil.getBean(beanMap.get(messageName)); + } + + public Method getMethod(String messageName){ + return methodMap.get(messageName); + } + + private PlatonMessageHandlerContext() { + Reflections reflections =new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("org.platon.p2p.handler")) + .setScanners(new MethodAnnotationsScanner())); + + Set methods = reflections.getMethodsAnnotatedWith(PlatonMessageType.class); + + for (Method method : methods) { + String className = method.getDeclaringClass().getName(); + String classSimpleName = method.getDeclaringClass().getSimpleName(); + + PlatonMessageType platonMessageType = method.getAnnotation(PlatonMessageType.class); + + Component component = method.getDeclaringClass().getAnnotation(Component.class); + if(component==null){ + logger.warn("Ignore the annotation:{} in {}", platonMessageType.value(), className); + continue; + }else{ + methodMap.put(platonMessageType.value(), method); + + beanMap.put(platonMessageType.value(), StringUtils.isBlank(component.value())? WordUtils.uncapitalize(classSimpleName) : component.value()); + } + } + } + + public static PlatonMessageHandlerContext getInstance() { + return PlatonMessageHandlerContext.SingletonContainer.instance; + } + + private static class SingletonContainer { + private static PlatonMessageHandlerContext instance = new PlatonMessageHandlerContext(); + } + + + + public static void main(String[] args){ + + + RoutableID destID = RoutableID.newBuilder().setId(ByteString.copyFromUtf8("1122")).setType(RoutableID.DestinationType.NODEIDTYPE).build(); + NodeID viaID = NodeID.newBuilder().setId(ByteString.copyFromUtf8("3344")).build(); + + Header header = Header.newBuilder().setTxId("txId").setTtl(10).addDest(destID).addVia(viaID).setMsgType("Ping").build(); + + AttachMessage attachMessage = AttachMessage.newBuilder().setNodeId(viaID).build(); + + Body message = Body.newBuilder().setData(Any.pack(attachMessage)).build(); + + PlatonMessage platonMessage = PlatonMessage.newBuilder().setHeader(header).setBody(message).build(); + + + Any any = platonMessage.getBody().getData(); + + + + + try { + Class clz = Class.forName("org.platon.p2p.proto.attach.AttachMessage"); + + Object msg = any.unpack(clz); + + Object test = new LinkService (); + Method method = PlatonMessageHandlerContext.getInstance().getMethod("AttachMessage"); + + Integer result = (Integer)method.invoke(test, header, msg); + System.out.println("result:" + result); + } catch (Exception e) { + e.printStackTrace(); + } + + byte[] a = ByteUtil.hostToBytes("127.0.0.1"); + byte[] b = ByteUtil.hostToBytes("127.0.1.1"); + + System.out.println( ByteUtils.equals(a, b)); + + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageType.java b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageType.java new file mode 100644 index 0000000..59da20c --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/handler/PlatonMessageType.java @@ -0,0 +1,26 @@ +package org.platon.p2p.handler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

PlatonMessageType.java

+ *

+ *

Description:

+ *

+ *

Copyright (c) 2017. juzix.io. All rights reserved.

+ *

+ * + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/30, lvxiaoyi, Initial Version. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface PlatonMessageType { + String value() default ""; +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/KadRoutingTable.java b/p2p/src/main/java/org/platon/p2p/plugins/KadRoutingTable.java new file mode 100644 index 0000000..d1d3377 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/KadRoutingTable.java @@ -0,0 +1,202 @@ +package org.platon.p2p.plugins; + +import com.alibaba.fastjson.JSON; +import com.google.protobuf.ByteString; +import org.apache.commons.collections.CollectionUtils; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.plugins.kademlia.Contact; +import org.platon.p2p.plugins.kademlia.KademliaRoutingTable; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @author yangzhou + * @create 2018-04-24 15:56 + */ +@Component("routingTable") +public class KadRoutingTable implements RoutingTable { + + private static Logger logger = LoggerFactory.getLogger(KadRoutingTable.class); + + + + private static KademliaRoutingTable table; + private static NodeID localNode; + private static final int numNodesRequired = 5; + + + public KadRoutingTable() { + localNode = NodeID.newBuilder() + .setId(NodeContext.localNodeId) + .setEndpoint(NodeContext.getEndpoint()) + .setPubKey(ByteString.copyFrom(NodeContext.publicKey)).build(); + + + this.table = new KademliaRoutingTable(); + this.table.insert(localNode); + } + + + @Override + public NodeID getLocalNode() { + List nodeIDList = this.table.findClosest(localNode,1); + if(CollectionUtils.isNotEmpty(nodeIDList)){ + return nodeIDList.get(0); + } + return localNode; + } + + + @Override + public List getNextHops(NodeID destination) { + return getNextHops(destination, numNodesRequired); + } + + + @Override + public List getNextHops(NodeID destination, int num) { + List nodeList = table.findClosest(destination, num); + if (localNode.getId().equals(destination.getId())){ + nodeList.remove(localNode); + } + return nodeList; + } + + + @Override + public List getNextHops(ResourceID destination) { + return getNextHops(destination, numNodesRequired); + } + + + @Override + public List getNextHops(ResourceID destination, int num) { + List nodeList = table.findClosest(destination, num); + return nodeList; + } + + + @Override + public List getNextHops(RoutableID destination){ + return getNextHops(destination, numNodesRequired); + } + + + @Override + public List getNextHops(RoutableID destination, int num){ + List nodeList = table.findClosest(destination, num); + if (destination.getType() == RoutableID.DestinationType.NODEIDTYPE) { + nodeList.remove(localNode); + } + return nodeList; + } + + + @Override + public String getEndPoint(ByteString destination) { + List nodeList = table.findClosest(destination, 1); + if (!nodeList.isEmpty() && nodeList.get(0).getId().equals(destination)) { + return nodeList.get(0).getEndpoint(); + } + return ""; + } + + + @Override + public List getNeighbors(){ + + List nodeList = table.findClosest(numNodesRequired); + nodeList.remove(localNode); + return nodeList; + } + + + @Override + public List getNeighbors(NodeID id, int num) { + return table.findClosest(id, num); + } + + + @Override + public List getNeighbors(ResourceID id, int num) { + return table.findClosest(id, num); + } + + + @Override + public List getNeighbors(RoutableID id, int num){ + return table.findClosest(id, num); + } + + + @Override + public void add(NodeID node) { + if (!node.getId().isEmpty() && !node.getEndpoint().isEmpty()) { + logger.debug("Add node {}/{} to local router table", CodecUtils.toHexString(node.getId()), node.getEndpoint()); + table.insert(node); + } + } + + + @Override + public void del(NodeID node) { + table.removeNode(node); + } + + + public void setUnresponsiveNode(NodeID node) { + table.setUnresponsiveContact(node); + } + + + @Override + public List getAllNode() { + return table.getAllNodes(); + } + + + @Override + public NodeID getNodeID(ByteString destination) { + List nodeList = table.findClosest(destination, 1); + if (!nodeList.isEmpty() && nodeList.get(0).getId().equals(destination)) { + return nodeList.get(0); + } + return null; + } + + + public NodeID getBucketOne(int i) { + return table.getBucketOne(i); + } + + + @Override + public String getTableToJson() { + return JSON.toJSONString(table.getAllContacts()); + } + + + @Override + public void setTableFromJson(String json) { + List contacts = JSON.parseArray(json, Contact.class); + for (Contact c : contacts) { + if (!localNode.getId().equals(c.getNode().getId())) { + table.insert(c); + } + } + } + + + @Override + public List getAllContacts() { + return table.getAllContacts(); + } + +} \ No newline at end of file diff --git a/p2p/src/main/java/org/platon/p2p/plugins/KadTopologyPlugin.java b/p2p/src/main/java/org/platon/p2p/plugins/KadTopologyPlugin.java new file mode 100644 index 0000000..f38e948 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/KadTopologyPlugin.java @@ -0,0 +1,196 @@ +package org.platon.p2p.plugins; + +import com.google.protobuf.Message; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.KadPluginConfig; +import org.platon.p2p.db.DB; +import org.platon.p2p.plugins.kademlia.KademliaHelp; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.plugin.JoinMessage; +import org.platon.p2p.proto.plugin.JoinRespMessage; +import org.platon.p2p.proto.plugin.QueryMessage; +import org.platon.p2p.proto.plugin.QueryRespMessage; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + + +/** + * @author yangzhou + * @create 2018-04-24 15:55 + */ +@Component("topologyPlugin") +public class KadTopologyPlugin implements TopologyPlugin { + + private static Logger logger = LoggerFactory.getLogger(KadTopologyPlugin.class); + + @Autowired + private KadRoutingTable kadRoutingTable; + @Autowired + private MessageRouter messageRouter; + + @Autowired + DB db; + + + + public void setKadRoutingTable(KadRoutingTable kadRoutingTable) { + this.kadRoutingTable = kadRoutingTable; + } + + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + + + + + + + + + + + + public void setDb(DB db) { + this.db = db; + } + + private final ScheduledExecutorService expiredRoutingInfoRemover = Executors.newScheduledThreadPool(1); + private final ScheduledExecutorService backupRoutable = Executors.newScheduledThreadPool(1); + + public KadTopologyPlugin() { + + + } + + + @Override + public void join(NodeID remote) { + + + + kadRoutingTable.add(remote); + + JoinMessage msg = JoinMessage.newBuilder().setNodeId(kadRoutingTable.getLocalNode()).build(); + + + RoutableID routableID = RoutableID.newBuilder().setType(RoutableID.DestinationType.NODEIDTYPE) + .setId(remote.getId()).build(); + List destinationList = Collections.singletonList(routableID); + + CompletableFuture future = messageRouter.sendRequest( msg, destinationList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + future.thenAcceptAsync(futMsg->{ + JoinRespMessage ansMsg = null; + try { + ansMsg = (JoinRespMessage)futMsg; + + List destList = new ArrayList<>(); + for (NodeID id : ansMsg.getNodeIdList()) { + if (!id.getId().equals(kadRoutingTable.getLocalNode().getId()) && !id.getId().equals(remote.getId())) { + destList.add(RoutableID.newBuilder().setId(id.getId()).setType(RoutableID.DestinationType.NODEIDTYPE).build()); + } + + } + + + + + + if(destList.size()>0) { + messageRouter.sendRequest(msg, destList, MessageRouter.ForwardingOptionType.BROADCAST_CONNECTION, false); + } + } catch (Exception e) { + logger.error("error:", e); + } + }, NodeContext.executor); + + } + + + @Override + public CompletableFuture> query(RoutableID dest) { + + + + final CompletableFuture> res = new CompletableFuture<>(); + + QueryMessage msg = QueryMessage.newBuilder().setRoutableId(dest).build(); + + CompletableFuture msgFut = messageRouter.sendRequest(msg, + Collections.singletonList(dest), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + msgFut.thenAcceptAsync(ans->{ + QueryRespMessage queryAns = (QueryRespMessage)ans; + res.complete(queryAns.getNodeIdList()); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + + return null; + }); + + + return res; + } + + + @Override + public int getDistance(RoutableID source, RoutableID dest){ + return KademliaHelp.getDistance(source, dest); + } + + + @Override + public boolean isLocalPeerResponsible(RoutableID dest){ + + List node = kadRoutingTable.getNextHops(dest, 1); + if (node.isEmpty()) return false; + return node.get(0).getId().equals(dest.getId()); + } + + + @Override + public boolean isLocalPeerValidStorage(ResourceID resourceId, boolean isReplica) { + return true; + } + + + @Override + public List getReplicaNodes(ResourceID resourceId) { + return kadRoutingTable.getNeighbors(); + } + + @Override + public RoutingTable getRoutingTable(){ + return kadRoutingTable; + } + + + @Override + public List getBroadCastNode(RoutableID dest) { + List nodes = new ArrayList<>(); + for (int i = 0; i < KadPluginConfig.getInstance().getIdLength()*8; i++){ + NodeID nodeID = kadRoutingTable.getBucketOne(i); + if (nodeID != null) { + nodes.add(nodeID); + } + } + return nodes; + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/Plugin.java b/p2p/src/main/java/org/platon/p2p/plugins/Plugin.java new file mode 100644 index 0000000..3ee183e --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/Plugin.java @@ -0,0 +1,117 @@ +package org.platon.p2p.plugins; + +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.plugin.JoinMessage; +import org.platon.p2p.proto.plugin.JoinRespMessage; +import org.platon.p2p.proto.plugin.QueryMessage; +import org.platon.p2p.proto.plugin.QueryRespMessage; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +; + + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/5/15, lvxiaoyi, Initial Version. + */ +@Component("plugin") +public class Plugin { + + private static Logger logger = LoggerFactory.getLogger(Plugin.class); + + + + @Autowired + private KadRoutingTable routingTable; + + @Autowired + private MessageRouter messageRouter; + + + public void setRoutingTable(KadRoutingTable routingTable) { + this.routingTable = routingTable; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + @PlatonMessageType("JoinMessage") + public void join(JoinMessage msg, HeaderHelper header){ + + + + List neighbors = routingTable.getNeighbors(msg.getNodeId(), 10); + routingTable.add(msg.getNodeId()); + JoinRespMessage ans = JoinRespMessage.newBuilder().addAllNodeId(neighbors).build(); + + + messageRouter.sendResponse(ans, header.txId(), + Collections.singletonList(RoutableID.newBuilder().setType(RoutableID.DestinationType.NODEIDTYPE).setId(msg.getNodeId().getId()).build()), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + + } + + + + + @PlatonMessageType("JoinRespMessage") + public void joinResp(JoinRespMessage msg, HeaderHelper header){ + + + for (NodeID id : msg.getNodeIdList()) { + routingTable.add(id); + } + messageRouter.handleResponse(header.txId(), msg); + + } + + + + + @PlatonMessageType("QueryMessage") + public void query(QueryMessage msg, HeaderHelper header){ + + + List neighbors = routingTable.getNeighbors(msg.getRoutableId(), 10); + QueryRespMessage ans = QueryRespMessage.newBuilder().addAllNodeId(neighbors).build(); + List destList = new ArrayList<>(); + for (NodeID nodeId : header.viaList()) { + destList.add(RoutableID.newBuilder().setId(nodeId.getId()).setType(RoutableID.DestinationType.NODEIDTYPE).build()); + } + + messageRouter.sendResponse(ans, header.txId(), + destList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + + } + + + + @PlatonMessageType("QueryRespMessage") + public void queryResp(QueryRespMessage msg, HeaderHelper header){ + logger.debug("route query response neighbor:", msg.toString()); + + + for (NodeID id : msg.getNodeIdList()) { + routingTable.add(id); + } + messageRouter.handleResponse(header.txId(), msg); + + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/PluginFactory.java b/p2p/src/main/java/org/platon/p2p/plugins/PluginFactory.java new file mode 100644 index 0000000..4bfc445 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/PluginFactory.java @@ -0,0 +1,18 @@ +package org.platon.p2p.plugins; + +/** + * @author yangzhou + * @create 2018-04-24 15:50 + */ +public abstract class PluginFactory { + PluginFactory factory; + + void register(PluginFactory factory) { + this.factory = factory; + } + + + TopologyPlugin create() { + return factory.create(); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/ResourceMap.java b/p2p/src/main/java/org/platon/p2p/plugins/ResourceMap.java new file mode 100644 index 0000000..b69aded --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/ResourceMap.java @@ -0,0 +1,61 @@ +package org.platon.p2p.plugins; + +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBException; +import org.platon.p2p.proto.common.ResourceID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author yangzhou + * @create 2018-07-23 10:25 + */ +public class ResourceMap { + + + @Autowired + DB db; + private static Logger logger = LoggerFactory.getLogger(ResourceMap.class); + + + private final Set resourceCache = new HashSet<>(); + + + + public void setDb(DB db) { + this.db = db; + } + + public boolean isExist(ResourceID resourceID) { + if (resourceCache.contains(resourceID)) { + return true; + } + + + try { + + if (db.get(resourceID.getId().toByteArray()) != null) { + return true; + } + if (db.hgetAll(resourceID.getId().toByteArray()) != null) { + return true; + } + } catch (DBException e) { + logger.error(e.getMessage()); + return false; + } + return false; + } + public void add(ResourceID resourceID) { + resourceCache.add(resourceID); + } + + public boolean remove(ResourceID resourceID) { + return resourceCache.remove(resourceID); + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/RoutableIDComparator.java b/p2p/src/main/java/org/platon/p2p/plugins/RoutableIDComparator.java new file mode 100644 index 0000000..94d9553 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/RoutableIDComparator.java @@ -0,0 +1,43 @@ +package org.platon.p2p.plugins; + +import org.platon.p2p.plugins.kademlia.KademliaHelp; +import org.platon.p2p.proto.common.RoutableID; + +import java.math.BigInteger; +import java.util.Comparator; + +/** + * @author yangzhou + * @create 2018-08-17 15:37 + */ +public class RoutableIDComparator implements Comparator { + private final BigInteger key; + + /** + * @param key The NodeId relative to which the distance should be measured. + */ + public RoutableIDComparator(RoutableID key) + { + this.key = KademliaHelp.getInt(key.getId()); + } + + /** + * Compare two objects which must both be of type Node + * and determine which is closest to the identifier specified in the + * constructor. + * + * @param n1 Node 1 to compare distance from the key + * @param n2 Node 2 to compare distance from the key + */ + @Override + public int compare(RoutableID n1, RoutableID n2) + { + BigInteger b1 = KademliaHelp.getInt(n1.getId()); + BigInteger b2 = KademliaHelp.getInt(n2.getId()); + + b1 = b1.xor(key); + b2 = b2.xor(key); + + return b1.abs().compareTo(b2.abs()); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/RoutingTable.java b/p2p/src/main/java/org/platon/p2p/plugins/RoutingTable.java new file mode 100644 index 0000000..385ee60 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/RoutingTable.java @@ -0,0 +1,48 @@ +package org.platon.p2p.plugins; + +import com.google.protobuf.ByteString; +import org.platon.p2p.plugins.kademlia.Contact; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; + +import java.util.List; + +/** + * @author yangzhou + * @create 2018-04-24 15:55 + */ +public interface RoutingTable { + + /** + * @return the the next hops node-ids + */ + NodeID getLocalNode(); + List getNextHops(NodeID destination); + List getNextHops(NodeID destination, int num); + + List getNextHops(ResourceID destination); + List getNextHops(ResourceID destination, int num); + + List getNextHops(RoutableID destination); + List getNextHops(RoutableID destination, int num); + + List getNeighbors(); + List getNeighbors(NodeID id, int num); + List getNeighbors(ResourceID id, int num); + List getNeighbors(RoutableID id, int num); + + List getAllNode(); + + String getEndPoint(ByteString destination); + + NodeID getNodeID(ByteString destination); + + void add(NodeID node); + void del(NodeID node); + String getTableToJson(); + + void setTableFromJson(String json); + + List getAllContacts(); +} \ No newline at end of file diff --git a/p2p/src/main/java/org/platon/p2p/plugins/TopologyPlugin.java b/p2p/src/main/java/org/platon/p2p/plugins/TopologyPlugin.java new file mode 100644 index 0000000..9df708e --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/TopologyPlugin.java @@ -0,0 +1,30 @@ +package org.platon.p2p.plugins; + +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * @author yangzhou + * @create 2018-04-24 15:53 + */ +public interface TopologyPlugin { + + void join(NodeID remote); + CompletableFuture> query(RoutableID dest); + + int getDistance(RoutableID source, RoutableID dest); + + boolean isLocalPeerResponsible(RoutableID dest); + + boolean isLocalPeerValidStorage(ResourceID resourceId, boolean isReplica); + + List getReplicaNodes(ResourceID resourceId); + + RoutingTable getRoutingTable(); + + List getBroadCastNode(RoutableID dest); +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/TopologyPluginFactory.java b/p2p/src/main/java/org/platon/p2p/plugins/TopologyPluginFactory.java new file mode 100644 index 0000000..684324a --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/TopologyPluginFactory.java @@ -0,0 +1,13 @@ +package org.platon.p2p.plugins; + +/** + * @author yangzhou + * @create 2018-04-24 15:51 + */ +public class TopologyPluginFactory extends PluginFactory { + + @Override + public TopologyPlugin create() { + return new KadTopologyPlugin(); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/kademlia/Contact.java b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/Contact.java new file mode 100644 index 0000000..2569f11 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/Contact.java @@ -0,0 +1,101 @@ +package org.platon.p2p.plugins.kademlia; + +import org.platon.p2p.proto.common.NodeID; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + + + +public class Contact implements Comparable +{ + + private NodeID n; + + + + private long lastSeen; + + + private int staleCount; + + + public Contact(){ + + } + public Contact(NodeID n) + { + this.n = n; + this.lastSeen = System.currentTimeMillis() / 1000L; + } + + public NodeID getNode() + { + return this.n; + } + + public void setNode(NodeID n) { + this.n = n; + } + + + public void setSeenNow() + { + this.lastSeen = System.currentTimeMillis() / 1000L; + } + + + public long lastSeen() + { + return this.lastSeen; + } + + + public void incrementStaleCount() + { + staleCount++; + } + + + public int staleCount() + { + return this.staleCount; + } + + + public void resetStaleCount() + { + this.staleCount = 0; + } + + @Override + public int compareTo(Contact o) + { + BigInteger from = new BigInteger(this.getNode().getId().toByteArray()); + BigInteger dest = new BigInteger(o.getNode().getId().toByteArray()); + return from.compareTo(dest); + + + + + + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Contact contact = (Contact) o; + return Arrays.equals(this.getNode().getId().toByteArray(), ((Contact)o).getNode().getId().toByteArray()); + } + + @Override + public int hashCode() { + + return Objects.hash(n.getId()); + } +} + + diff --git a/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaBucket.java b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaBucket.java new file mode 100644 index 0000000..10412ed --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaBucket.java @@ -0,0 +1,252 @@ +package org.platon.p2p.plugins.kademlia; + +import org.apache.commons.codec.binary.Hex; +import org.platon.p2p.common.KadPluginConfig; +import org.platon.p2p.proto.common.NodeID; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.TreeSet; + + + +public class KademliaBucket +{ + + + private final int depth; + + + private final TreeSet contacts; + + + private final TreeSet replacementCache; + + + { + contacts = new TreeSet<>(); + replacementCache = new TreeSet<>(); + } + + + public KademliaBucket(int depth) + { + this.depth = depth; + + } + + + public synchronized void insert(Contact c) + { + if (this.contacts.contains(c)) + { + + this.removeFromContacts(c.getNode()); + c.setSeenNow(); + c.resetStaleCount(); + this.contacts.add(c); + } + else + { + + if (contacts.size() >= KadPluginConfig.getInstance().getKValue()) + { + + Contact stalest = null; + for (Contact tmp : this.contacts) + { + if (tmp.staleCount() >= KadPluginConfig.getInstance().getStaleTimes()) + { + + if (stalest == null) + { + stalest = tmp; + } + else if (tmp.staleCount() > stalest.staleCount()) + { + stalest = tmp; + } + } + } + + + if (stalest != null) + { + this.contacts.remove(stalest); + this.contacts.add(c); + } + else + { + + this.insertIntoReplacementCache(c); + } + } + else + { + this.contacts.add(c); + } + } + } + + public synchronized void insert(NodeID n) + { + this.insert(new Contact(n)); + } + + public synchronized boolean containsContact(Contact c) + { + return this.contacts.contains(c); + } + + public synchronized boolean containsNode(NodeID n) + { + return this.containsContact(new Contact(n)); + } + + public synchronized boolean removeContact(Contact c) + { + + if (!this.contacts.contains(c)) + { + return false; + } + + + if (!this.replacementCache.isEmpty()) + { + + this.contacts.remove(c); + Contact replacement = this.replacementCache.first(); + this.contacts.add(replacement); + this.replacementCache.remove(replacement); + } + else + { + + this.getFromContacts(c.getNode()).incrementStaleCount(); + } + + return true; + } + + private synchronized Contact getFromContacts(NodeID n) + { + for (Contact c : this.contacts) + { + if (c.getNode().equals(n)) + { + return c; + } + } + + + throw new NoSuchElementException("The contact does not exist in the contacts list."); + } + + public synchronized Contact removeFromContacts(NodeID n) + { + for (Contact c : this.contacts) + { + if (c.getNode().getId().equals(n.getId())) + { + this.contacts.remove(c); + return c; + } + } + + + throw new NoSuchElementException("Node does not exist in the replacement cache. "); + } + + public synchronized boolean removeNode(NodeID n) + { + return this.removeContact(new Contact(n)); + } + + public synchronized int numContacts() + { + return this.contacts.size(); + } + + public synchronized int getDepth() + { + return this.depth; + } + + public synchronized List getContacts() + { + final ArrayList ret = new ArrayList<>(); + + + if (this.contacts.isEmpty()) + { + return ret; + } + + + for (Contact c : this.contacts) + { + ret.add(c); + } + + return ret; + } + + + private synchronized void insertIntoReplacementCache(Contact c) + { + + if (this.replacementCache.contains(c)) + { + + Contact tmp = this.removeFromReplacementCache(c.getNode()); + tmp.setNode(c.getNode()); + tmp.setSeenNow(); + this.replacementCache.add(tmp); + } + else if (this.replacementCache.size() > KadPluginConfig.getInstance().getKValue()) + { + + this.replacementCache.remove(this.replacementCache.last()); + this.replacementCache.add(c); + } + else + { + this.replacementCache.add(c); + } + } + + private synchronized Contact removeFromReplacementCache(NodeID n) + { + for (Contact c : this.replacementCache) + { + if (c.getNode().equals(n)) + { + this.replacementCache.remove(c); + return c; + } + } + + + throw new NoSuchElementException("Node does not exist in the replacement cache. "); + } + + public synchronized String toString() + { + StringBuilder sb = new StringBuilder("Bucket at depth: "); + sb.append(this.depth); + sb.append("\n Nodes: \n"); + for (Contact n : this.contacts) + { + sb.append("Node: "); + sb.append("nodeId:" + Hex.encodeHexString(n.getNode().getId().toByteArray()) + " localNodeEndpoint:" + n.getNode().getEndpoint()); + sb.append(" (stale: "); + sb.append(n.staleCount()); + sb.append(")"); + sb.append("\n"); + } + + return sb.toString(); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaHelp.java b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaHelp.java new file mode 100644 index 0000000..05256e7 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaHelp.java @@ -0,0 +1,94 @@ +package org.platon.p2p.plugins.kademlia; + +import com.google.protobuf.ByteString; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.proto.common.RoutableID; + +import java.math.BigInteger; + + +public class KademliaHelp { + + public static int getDistance(RoutableID from, RoutableID to) + { + + return getDistance(from.getId(), to.getId()); + } + + public static int getDistance(ByteString from, ByteString to) + { + + assert(from.size() == to.size()); + int length = from.toByteArray().length*8; + return length - getFirstSetBitIndex(xor(from, to)) - 1; + } + + public static byte[] xor(RoutableID from, RoutableID to) + { + return xor(from.getId(), to.getId()); + } + + public static byte[] xor(ByteString from, ByteString to) + { + assert(from.toByteArray().length == to.toByteArray().length); + int length = from.toByteArray().length*8; + byte[] result = new byte[length / 8]; + byte[] nidBytes = to.toByteArray(); + + for (int i = 0; i < length / 8; i++) + { + result[i] = (byte) (from.toByteArray()[i] ^ nidBytes[i]); + } + + return result; + } + + public static int getFirstSetBitIndex(byte[] keyBytes) + { + int prefixLength = 0; + + for (byte b : keyBytes) + { + if (b == 0) + { + prefixLength += 8; + } + else + { + + int count = 0; + for (int i = 7; i >= 0; i--) + { + boolean a = (b & (1 << i)) == 0; + if (a) + { + count++; + } + else + { + break; + } + } + + + prefixLength += count; + + + break; + } + } + return prefixLength; + } + public static BigInteger getInt(RoutableID n) { + return new BigInteger(1, n.getId().toByteArray()); + } + + public static BigInteger getInt(ByteString n) { + return new BigInteger(1, n.toByteArray()); + } + + public static BigInteger getInt(Bytes n) { + return new BigInteger(1, n.getKey()); + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaRoutingTable.java b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaRoutingTable.java new file mode 100644 index 0000000..69b5740 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KademliaRoutingTable.java @@ -0,0 +1,232 @@ +package org.platon.p2p.plugins.kademlia; + +import com.google.protobuf.ByteString; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.KadPluginConfig; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + + + +public class KademliaRoutingTable +{ + + private static NodeID localNode; + private static KademliaBucket[] buckets; + + + + public KademliaRoutingTable() { + + localNode = NodeID.newBuilder() + .setId(NodeContext.localNodeId) + .setEndpoint(NodeContext.getEndpoint()) + .setPubKey(ByteString.copyFrom(NodeContext.publicKey)).build(); + + + + this.initialize(); + + + this.insert(localNode); + } + + + /* public KademliaRoutingTable(NodeID localNode) + { + this.localNode = localNode; + * + this.initialize(); + + * + this.insert(localNode); + }*/ + + + public final void initialize() { + + this.buckets = new KademliaBucket[KadPluginConfig.getInstance().getIdLength()*8]; + for (int i = 0; i < KadPluginConfig.getInstance().getIdLength()*8; i++) + { + buckets[i] = new KademliaBucket(i); + } + } + + + public synchronized final void insert(Contact c) + { + this.buckets[this.getBucketId(c.getNode())].insert(c); + } + + + + public synchronized final void insert(NodeID n) + { + this.buckets[this.getBucketId(n)].insert(n); + } + + + + public final int getBucketId(NodeID nid) + { + int bId = KademliaHelp.getDistance(this.localNode.getId(), nid.getId()); + + + return bId < 0 ? 0 : bId; + } + + + + public final List findClosest(ResourceID target, int numNodesRequired) { + return findClosest(target.getId(), numNodesRequired); + } + + public final List findClosest(NodeID target, int numNodesRequired) { + return findClosest(target.getId(), numNodesRequired); + } + + public final List findClosest(RoutableID target, int numNodesRequired) { + return findClosest(target.getId(), numNodesRequired); + } + + + public synchronized final List findClosest(ByteString target, int numNodesRequired) + { + NodeID tagetNodeId = NodeID.newBuilder().setId(target).build(); + TreeSet sortedSet = new TreeSet<>(new KeyComparator(tagetNodeId)); + sortedSet.addAll(this.getAllNodes()); + + List closest = new ArrayList<>(numNodesRequired); + + + int count = 0; + for (NodeID n : sortedSet) + { + closest.add(n); + if (++count == numNodesRequired) + { + break; + } + } + return closest; + } + + public synchronized final List findClosest(int numNodesRequired) + { + return findClosest(localNode.getId(), numNodesRequired); + } + + + public synchronized final List getAllNodes() + { + List nodes = new ArrayList<>(); + + for (KademliaBucket b : this.buckets) + { + for (Contact c : b.getContacts()) + { + + if (c.staleCount() < KadPluginConfig.getInstance().getStaleTimes()) { + nodes.add(c.getNode()); + } + } + } + + return nodes; + } + + public synchronized final NodeID getBucketOne(int index) { + if (this.buckets[index].getContacts().isEmpty()) { + return null; + } + return this.buckets[index].getContacts().get(0).getNode(); + } + + public final List getAllContacts() + { + List contacts = new ArrayList<>(); + + for (KademliaBucket b : this.buckets) + { + contacts.addAll(b.getContacts()); + } + + return contacts; + } + + + + public final KademliaBucket[] getBuckets() + { + return this.buckets; + } + + + public final void setBuckets(KademliaBucket[] buckets) + { + this.buckets = buckets; + } + + + public void setUnresponsiveContacts(List contacts) + { + if (contacts.isEmpty()) + { + return; + } + for (NodeID n : contacts) + { + this.setUnresponsiveContact(n); + } + } + + + public synchronized void setUnresponsiveContact(NodeID n) + { + int bucketId = this.getBucketId(n); + + + this.buckets[bucketId].removeNode(n); + } + + public synchronized void removeNode(NodeID n) { + int bucketId = this.getBucketId(n); + this.buckets[bucketId].removeFromContacts(n); + } + + @Override + public synchronized final String toString() + { + StringBuilder sb = new StringBuilder("\nPrinting Routing Table Started ***************** \n"); + int totalContacts = 0; + for (KademliaBucket b : this.buckets) + { + if (b.numContacts() > 0) + { + totalContacts += b.numContacts(); + sb.append("# nodes in Bucket with depth "); + sb.append(b.getDepth()); + sb.append(": "); + sb.append(b.numContacts()); + sb.append("\n"); + sb.append(b.toString()); + sb.append("\n"); + } + } + + sb.append("\nTotal Contacts: "); + sb.append(totalContacts); + sb.append("\n\n"); + + sb.append("Printing Routing Table Ended ******************** "); + + return sb.toString(); + } + +} + diff --git a/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KeyComparator.java b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KeyComparator.java new file mode 100644 index 0000000..650996b --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/plugins/kademlia/KeyComparator.java @@ -0,0 +1,33 @@ +package org.platon.p2p.plugins.kademlia; + +import org.platon.p2p.proto.common.NodeID; + +import java.math.BigInteger; +import java.util.Comparator; + + +public class KeyComparator implements Comparator +{ + + private final BigInteger key; + + + public KeyComparator(NodeID key) + { + this.key = KademliaHelp.getInt(key.getId()); + } + + + @Override + public int compare(NodeID n1, NodeID n2) + { + BigInteger b1 = KademliaHelp.getInt(n1.getId()); + BigInteger b2 = KademliaHelp.getInt(n2.getId()); + + b1 = b1.xor(key); + b2 = b2.xor(key); + + return b1.abs().compareTo(b2.abs()); + } +} + diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/MessageIdCache.java b/p2p/src/main/java/org/platon/p2p/pubsub/MessageIdCache.java new file mode 100644 index 0000000..98ecc8f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/MessageIdCache.java @@ -0,0 +1,55 @@ +package org.platon.p2p.pubsub; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author yangzhou + * @create 2018-08-01 16:08 + */ +public class MessageIdCache { + + private final Map> msgCache = new HashMap<>(); + + MessageIdCache() { + + } + + + void add(String topic, String msgId) { + synchronized (msgCache) { + + Set msgSet = msgCache.get(topic); + if (msgSet == null) { + msgSet = new HashSet<>(); + } + + msgSet.add(msgId); + msgCache.put(topic, msgSet); + } + } + + Set get(String topic) { + synchronized (msgCache) { + return msgCache.get(topic); + } + } + + void sweep(){ + synchronized (msgCache) { + msgCache.clear(); + } + } + + String dump() { + StringBuilder stringBuilder = new StringBuilder(); + synchronized (msgCache) { + for (Map.Entry> entry : msgCache.entrySet()) { + stringBuilder.append(String.format("topic:%s size:%d,", entry.getKey(), entry.getValue().size())); + } + } + return stringBuilder.toString(); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/PubSub.java b/p2p/src/main/java/org/platon/p2p/pubsub/PubSub.java new file mode 100644 index 0000000..0f6fb3f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/PubSub.java @@ -0,0 +1,288 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import io.netty.util.internal.ConcurrentSet; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.pubsub.EntryMessage; +import org.platon.p2p.proto.pubsub.SubMessage; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-23 16:33 + */ +@Component("pubSub") +public class PubSub { + static Logger logger = LoggerFactory.getLogger(PubSub.class); + + private final Map topicValidators = new ConcurrentHashMap<>(); + private final Map> topics = new ConcurrentHashMap<>(); + private final Map> mytopics = new HashMap<>(); + private final TimeCache seenMessage = new TimeCache(10); + + + private final Set peers = new HashSet<>(); + + public PubSubRouter pubSubRouter = new PubSubRouter(); + + + @Autowired + private RoutingTable routingTable; + + + + @Autowired + private MessageRouter messageRouter; + + + + + + + + + + + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + void registerTopicValidator(String topic, TopicValidator validator) { + topicValidators.put(topic, validator); + } + + void unregisterTopicValidator(String topic){ + topicValidators.remove(topic); + } + + public void addPeer(ByteString peer) { + synchronized (peers) { + peers.add(Bytes.valueOf(peer)); + } + } + + public void removePeer(ByteString peer) { + synchronized (peers) { + peers.remove(Bytes.valueOf(peer)); + } + pubSubRouter.removePeer(Bytes.valueOf(peer)); + } + + public Map> getTopic() { + return topics; + } + + + + public void subscribe(String topic, String serviceName, SubscribeCallback callback) { + logger.trace("subscribe:{}, serviceName:{} local:{}", topic, serviceName, NodeUtils.getNodeIdString(routingTable.getLocalNode().getId())); + + boolean needAnnounce = false; + + synchronized (mytopics) { + Map subscribeCallbackMap = mytopics.get(topic); + if (subscribeCallbackMap != null) { + if(!subscribeCallbackMap.containsKey(serviceName)) { + subscribeCallbackMap.put(serviceName, callback); + + } + } else { + subscribeCallbackMap = new HashMap<>(); + subscribeCallbackMap.put(serviceName, callback); + mytopics.put(topic, subscribeCallbackMap); + needAnnounce = true; + } + } + + logger.trace("needAnnounce:{}", needAnnounce); + + if (needAnnounce) { + announce(topic, true); + pubSubRouter.join(topic); + } + } + + + + private void announce(String topic, boolean sub) { + + TopicMessage.Builder topicMessageBuilder = TopicMessage.newBuilder(); + SubMessage.Builder subMessageBuilder = SubMessage.newBuilder(); + + subMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + subMessageBuilder.setSub(sub); + subMessageBuilder.setTopic(topic); + + topicMessageBuilder.setFromNodeId(routingTable.getLocalNode().getId()); + topicMessageBuilder.setSubscribe(subMessageBuilder); + + synchronized (peers) { + CompletableFuture.runAsync(()-> { + for (Bytes peer : peers) { + logger.trace("send announce:{} local:{}", NodeUtils.getNodeIdString(peer.getKey()), NodeUtils.getNodeIdString(routingTable.getLocalNode().getId())); + messageRouter.sendRequest(topicMessageBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(peer, RoutableID.DestinationType.NODEIDTYPE)), + MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + } + }); + } + } + + + + + + public void unSubscribe(String topic, String serviceName) { + boolean needAnnounce = false; + + synchronized (mytopics) { + Map subscribeCallbackMap = mytopics.get(topic); + if (subscribeCallbackMap != null) { + if(subscribeCallbackMap.containsKey(serviceName)) { + subscribeCallbackMap.remove(serviceName); + if (subscribeCallbackMap.isEmpty()) { + logger.debug("unsubscribe topic:{} and send other peers", topic); + needAnnounce = true; + } + } + } + } + + if (needAnnounce) { + announce(topic, false); + pubSubRouter.leave(topic); + } + } + + + public void publish(String topic, byte[] data) { + logger.trace("publish:{}", topic); + + EntryMessage.Builder entryMessageBuilder = EntryMessage.newBuilder(); + + entryMessageBuilder.setData(ByteString.copyFrom(data)); + entryMessageBuilder.setFromNodeId(routingTable.getLocalNode().getId()); + entryMessageBuilder.setKey(UUID.randomUUID().toString()); + entryMessageBuilder.setTopic(topic); + + + pushMsg(entryMessageBuilder.build()); + + + + + + + + + } + + + + private void pushMsg(EntryMessage entryMessage) { + logger.trace("push msg topic:{}", entryMessage.getTopic()); + + String msgId = msgId(entryMessage); + if (seenMessage.has(msgId)) { + logger.trace("haved seen msgid{}", msgId); + return; + } + + seenMessage.add(msgId); + notifyAll(entryMessage.getTopic(), entryMessage.getData().toByteArray()); + pubSubRouter.publish(Bytes.valueOf(entryMessage.getFromNodeId()), entryMessage); + + } + + public static String msgId(EntryMessage entryMessage) { + return NodeUtils.getNodeIdString(entryMessage.getFromNodeId()) + entryMessage.getKey(); + } + + + + public void notifyAll(String topic, byte[] data) { + Map callbackMap = mytopics.get(topic); + if (callbackMap != null) { + for (SubscribeCallback callback : callbackMap.values()) { + if (callback != null) { + callback.subscribe(topic, data); + } + } + } + + } + + public Set listPeers(String topic) { + return topics.get(topic); + } + + public interface TopicValidator { + boolean validatorMsg(Message message); + } + + public interface SubscribeCallback { + void subscribe(String topic, byte[] data); + } + + + + public void sendMessage(TopicMessage msg, HeaderHelper header){ + logger.trace("receive topic message from:{} has Subscribe:{}", NodeUtils.getNodeIdString(msg.getFromNodeId()), msg.hasSubscribe()); + + if (msg.hasSubscribe()) { + logger.trace("announce msg:{} ", msg.getSubscribe().getSub()); + if (msg.getSubscribe().getSub()){ + synchronized (topics) { + Set nodeIds = topics.get(msg.getSubscribe().getTopic()); + if (nodeIds == null) { + + nodeIds = new ConcurrentSet<>(); + } + nodeIds.add(Bytes.valueOf(msg.getSubscribe().getNodeId())); + topics.put(msg.getSubscribe().getTopic(), nodeIds); + } + } else { + Set nodeIds = topics.get(msg.getSubscribe().getTopic()); + if (nodeIds != null) { + nodeIds.remove(msg.getSubscribe().getNodeId()); + } + } + } + + if (msg.getPublishedEntryCount() != 0) { + for (EntryMessage entryMessage : msg.getPublishedEntryList()){ + logger.trace("receive remote:{} have publish message:{}", NodeUtils.getNodeIdString(entryMessage.getFromNodeId()), entryMessage.getKey()); + pushMsg(entryMessage); + } + } + + pubSubRouter.handleControl(Bytes.valueOf(msg.getFromNodeId()), msg.getControl()); + } + + + public boolean isSubscribe(String topic){ + synchronized (mytopics) { + return mytopics.containsKey(topic); + } + } +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/PubSubMessageHook.java b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubMessageHook.java new file mode 100644 index 0000000..9782dae --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubMessageHook.java @@ -0,0 +1,20 @@ +package org.platon.p2p.pubsub; + +import org.platon.p2p.MessageHook; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author yangzhou + * @create 2018-07-26 10:09 + */ +public class PubSubMessageHook implements MessageHook.MessageCallback { + private static Logger logger = LoggerFactory.getLogger(PubSubMessageHook.class); + @Override + public boolean isNeedProcess(PlatonMessage request) { + logger.trace("PubSubMessageHook need process"); + + return true; + } +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/PubSubRouter.java b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubRouter.java new file mode 100644 index 0000000..c408305 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubRouter.java @@ -0,0 +1,606 @@ +package org.platon.p2p.pubsub; + +import org.apache.http.util.Asserts; +import org.platon.common.cache.DelayCache; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.pubsub.*; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author yangzhou + * @create 2018-07-23 17:06 + */ +public class PubSubRouter { + + private static Logger logger = LoggerFactory.getLogger(PubSubRouter.class); + + private static final int SubHeartbeatInterval = 5000; + private static final int SubCount = 6; + private static final int MsgCacheTime = 1000000; + private static final int FantOutTTL = 60 * 1000; + private Map> mesh = new ConcurrentHashMap<>(); + private Map> fanout = new ConcurrentHashMap<>(); + private Map lastpub = new ConcurrentHashMap<>(); + + private Map> unSendMessage = new ConcurrentHashMap<>(); + private MessageIdCache messageIdCache = new MessageIdCache(); + private DelayCache msgCache = new DelayCache<>(); + private PubSub pubSub; + + @Autowired + private MessageRouter messageRouter; + + @Autowired + private RoutingTable routingTable; + + public PubSubRouter() { + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + public void attach(PubSub pubSub) { + this.pubSub = pubSub; + Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(new Runnable(){ + + @Override + public void run() { + heartbeat(); + + } + }, SubHeartbeatInterval, SubHeartbeatInterval, TimeUnit.MILLISECONDS); + + } + + + + + + + + + public void removePeer(Bytes peer) { + + for (Set peers : mesh.values()) { + peers.remove(peer); + } + + for (Set peers : fanout.values()) { + peers.remove(peer); + } + } + + + + public void publish(Bytes from, EntryMessage msg) { + logger.trace("publish from:{}", NodeUtils.getNodeIdString(from.getKey())); + + String msgId = PubSub.msgId(msg); + messageIdCache.add(msg.getTopic(), msgId); + msgCache.put(msgId, msg, MsgCacheTime, TimeUnit.MILLISECONDS); + + String topic = msg.getTopic(); + Set nodeIds = mesh.get(topic); + if (nodeIds == null || nodeIds.isEmpty()) { + + nodeIds = fanout.get(topic); + if (nodeIds == null || nodeIds.isEmpty()) { + + nodeIds = getPeers(topic, SubCount); + if (nodeIds != null && !nodeIds.isEmpty()) { + fanout.put(topic, nodeIds); + } + } + lastpub.put(topic, System.currentTimeMillis()); + } + + if (nodeIds != null && !nodeIds.isEmpty()) { + for (Bytes peer : nodeIds) { + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + + topicMessageBuilder.addPublishedEntry(msg); + + sendMessage(peer, topicMessageBuilder); + } + } + } + + + + private void sendMessage(Bytes peer, TopicMessage.Builder topicMessageBuilder) { + + logger.trace("send peer:{}", NodeUtils.getNodeIdString(peer.getKey())); + CompletableFuture.runAsync(()-> { + try { + + Map iHaveMessageMap = unSendMessage.get(peer); + + if (iHaveMessageMap != null) { + + for (Map.Entry entry : iHaveMessageMap.entrySet()) { + + topicMessageBuilder.getControlBuilder().addIhave(entry.getValue()); + unSendMessage.remove(peer); + } + } + + logger.trace("send message local:{} -> remote:{}", NodeUtils.getNodeIdString(routingTable.getLocalNode().getId()), NodeUtils.getNodeIdString(peer.getKey())); + + messageRouter.sendRequest(topicMessageBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(peer, RoutableID.DestinationType.NODEIDTYPE)), + MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + }catch (Exception e) { + logger.error("error:", e); + } + }); + } + + + + + private Set getPeers(String topic, int count) { + Map> topics = pubSub.getTopic(); + Set peers = topics.get(topic); + if (peers == null || peers.isEmpty()) { + return null; + } + + return shufflePeers(peers, count); + } + + + + + private Set shufflePeers(Set peers, int count) { + Set nodeIDSet = ConcurrentHashMap.newKeySet(); + + Asserts.notNull(peers, "peers is null"); + if (peers.size() < count) { + nodeIDSet.addAll(peers); + return nodeIDSet; + } + + List list = new ArrayList(peers); + for (int i = 0; i < peers.size(); i++) { + Random random = new Random(); + int j = random.nextInt() % (i+1); + Collections.swap(list, i, j); + } + + nodeIDSet.addAll(list); + return nodeIDSet; + } + + + + public void handleControl(Bytes from, ControlMessage msg){ + if (msg == null) { + logger.trace("receive control message is null"); + return; + } + + List iwant = handleIHave(msg.getIhaveList()); + List ihave = handleIWant(msg.getIwantList()); + PruneMessage prune = handleGraft(msg.getGraft()); + handlePrune(msg.getPrune()); + + logger.trace("iwant is {}, ihave is {} prune is {}", + iwant == null ? "none":"have", + ihave == null ? "none":"have", + prune == null ? "none":"have"); + + if (iwant == null && ihave == null && prune == null) { + + return; + } + + + + + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + + if (ihave != null) { + topicMessageBuilder.addAllPublishedEntry(ihave); + } + + ControlMessage.Builder controlMessageBuilder = ControlMessage.newBuilder(); + if (iwant != null) { + controlMessageBuilder.addAllIwant(iwant); + } + + if (prune != null) { + controlMessageBuilder.setPrune(prune); + } + + topicMessageBuilder.setControl(controlMessageBuilder); + + sendMessage(from, topicMessageBuilder); + + } + + + private List handleIHave(List msg) { + if (msg == null) { + logger.trace("IHaveMessage is null"); + return null; + } + + List iWantMessageList = new LinkedList<>(); + for (IHaveMessage ihave : msg) { + logger.trace("ihave:getTopic:{}, nodeId size:{} mesh size:{}", ihave.getTopic(), ihave.getMessageIdList().size(), mesh.size()); + if (!mesh.containsKey(ihave.getTopic())) { + continue; + } + + IWantMessage.Builder iWantMessageBuild = IWantMessage.newBuilder(); + + iWantMessageBuild.setTopic(ihave.getTopic()); + + for (String messageID : ihave.getMessageIdList()) { + EntryMessage entryMessage = msgCache.get(messageID); + if (entryMessage == null) { + iWantMessageBuild.addMessageId(messageID); + } + } + + iWantMessageList.add(iWantMessageBuild.build()); + } + + if (iWantMessageList.isEmpty()) { + return null; + } + return iWantMessageList; + } + + + + private List handleIWant(List msg) { + if (msg == null) { + logger.trace("IWantMessage is null"); + return null; + } + List entryMessageList = new LinkedList<>(); + for (IWantMessage iwant : msg) { + if(!mesh.containsKey(iwant.getTopic())){ + continue; + } + for(String messageID : iwant.getMessageIdList()) { + EntryMessage entryMessage = msgCache.get(messageID); + + if (entryMessage != null) { + entryMessageList.add(entryMessage); + } + } + } + if (entryMessageList.isEmpty()) { + return null; + } + return entryMessageList; + } + + + private PruneMessage handleGraft(GraftMessage msg) { + logger.trace("handle graft"); + + if (msg == null) { + logger.trace("graft is null"); + return null; + } + + + + List pruneTopics = new LinkedList<>(); + + for (String topic : msg.getTopicList()) { + Set peers = mesh.get(topic); + if (peers == null) { + pruneTopics.add(topic); + } else { + peers.add(Bytes.valueOf(msg.getNodeId())); + } + } + + if (pruneTopics.isEmpty()) { + return null; + } + + PruneMessage.Builder pruneMessageBuilder = PruneMessage.newBuilder(); + pruneMessageBuilder.addAllTopic(pruneTopics); + pruneMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + + return pruneMessageBuilder.build(); + } + + + + + private void handlePrune(PruneMessage msg) { + if (msg == null) { + logger.trace("prune message is null"); + return; + } + for (String topic : msg.getTopicList()) { + logger.trace("handle pruned nodeid:{} topic:{}", NodeUtils.getNodeIdString(msg.getNodeId()), topic); + Set peers = mesh.get(topic); + if (peers != null) { + logger.trace("handle pruned removeSessionFuture:{}", NodeUtils.getNodeIdString(msg.getNodeId())); + peers.remove(Bytes.valueOf(msg.getNodeId())); + } + } + } + + + + private void sendGraft(Bytes peer, String topic) { + logger.trace("send graft:{} topic:{}", NodeUtils.getNodeIdString(peer.getKey()), topic); + + + + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + + ControlMessage.Builder controlMessageBuilder = ControlMessage.newBuilder(); + GraftMessage.Builder graftMessageBuilder = GraftMessage.newBuilder(); + + graftMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + graftMessageBuilder.addTopic(topic); + + controlMessageBuilder.setGraft(graftMessageBuilder); + topicMessageBuilder.setControl(controlMessageBuilder); + + sendMessage(peer, topicMessageBuilder); + } + + + + private void sendPrune(Bytes peer, String topic) { + logger.trace("send Prune topic:{}", topic); + + + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + + ControlMessage.Builder controlMessageBuilder = ControlMessage.newBuilder(); + PruneMessage.Builder pruneMessageBuilder = PruneMessage.newBuilder(); + + pruneMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + pruneMessageBuilder.addTopic(topic); + + controlMessageBuilder.setPrune(pruneMessageBuilder); + topicMessageBuilder.setControl(controlMessageBuilder); + + sendMessage(peer, topicMessageBuilder); + } + + + + public void join(String topic) { + logger.trace("join topic:{}", topic); + if (mesh.containsKey(topic)) { + return; + } + + Set nodeIds = fanout.get(topic); + if (nodeIds != null && !nodeIds.isEmpty()) { + mesh.put(topic, nodeIds); + fanout.remove(topic); + } else { + nodeIds = getPeers(topic, SubCount); + if (nodeIds == null){ + nodeIds = ConcurrentHashMap.newKeySet(); + } + mesh.put(topic, nodeIds); + } + + logger.trace("mesh size:{} nodeIds size:{}", mesh.size(), nodeIds.size()); + for (Bytes peer : nodeIds) { + logger.trace("send graft to :{}", peer); + sendGraft(peer, topic); + } + } + + + + public void leave(String topic) { + logger.trace("leave topic:{}" , topic); + + Set peers = mesh.remove(topic); + if (peers != null) { + logger.trace("send prunce:{}", topic); + for (Bytes peer : peers) { + + sendPrune(peer, topic); + } + } + } + + + + + public void heartbeat() { + logger.trace("heartbeat.........."); + + Map> toGraft = new HashMap<>(); + Map> toPrune = new HashMap<>(); + + logger.trace("msgCache:{}, pubsub:{} mesh size:{} hashcode:{} fanout size:{}", messageIdCache.dump(), System.identityHashCode(this), mesh.size(), System.identityHashCode(mesh), fanout.size()); + + for (Map.Entry> topicPeers : mesh.entrySet()) { + String topic = topicPeers.getKey(); + Set peers = topicPeers.getValue(); + + if (peers.size() > SubCount) { + logger.trace("topic:{} mesh peers size > {}", topic, SubCount); + + int count = peers.size() - SubCount; + Set plst = shufflePeers(peers, count); + + for (Bytes peer : plst) { + List topics = toPrune.getOrDefault(peer, new LinkedList<>()); + topics.add(topic); + toPrune.putIfAbsent(peer, topics); + } + } else if (peers.size() < SubCount){ + logger.trace("topic:{} mesh peers size < {}", topic, SubCount); + if (mesh.size() == 1) { + logger.trace("mesh size:{} fanout size:{}", mesh.size(), fanout.size()); + } + try { + + + int count = SubCount - peers.size(); + Set plst = getPeers(topic, count); + + if (plst != null) { + peers.addAll(plst); + + for (Bytes peer : plst) { + List topics = toGraft.getOrDefault(peer, new LinkedList<>()); + topics.add(topic); + toGraft.putIfAbsent(peer, topics); + } + } + }catch (Exception e) { + logger.error("error:", e); + } + } + logger.trace("mesh size:{} fanout size:{}", mesh.size(), fanout.size()); + emitMessage(topic, peers); + logger.trace("mesh size:{} fanout size:{}", mesh.size(), fanout.size()); + } + + long now = System.currentTimeMillis(); + for (Iterator> it = lastpub.entrySet().iterator(); it.hasNext();){ + Map.Entry item = it.next(); + if (item.getValue() - now > FantOutTTL) { + fanout.remove(item.getKey()); + it.remove(); + } + } + + + + + for (Map.Entry> topicPeers : fanout.entrySet()) { + String topic = topicPeers.getKey(); + Set peers = topicPeers.getValue(); + + if (peers.size() < SubCount){ + + Set plst = getPeers(topic, SubCount); + if (plst != null) { + peers.addAll(plst); + for (Bytes peer : plst) { + emitMessage(topic, peers); + } + } + } + } + logger.trace("mesh size:{} fanout size:{}", mesh.size(), fanout.size()); + for (Map.Entry> graft : toGraft.entrySet()) { + logger.trace("graft:{}", NodeUtils.getNodeIdString(graft.getKey().getKey())); + } + sendGraftAndPrune(toGraft, toPrune); + messageIdCache.sweep(); + logger.trace("mesh size:{} fanout size:{}", mesh.size(), fanout.size()); + } + + + private void sendGraftAndPrune(Map> toGraft, Map> toPrune) { + logger.trace("toGraft size:{}, toPrune size:{}", toGraft.size(), toPrune.size()); + + + CompletableFuture.runAsync(()->{ + for (Map.Entry> graft : toGraft.entrySet()) { + try { + logger.trace("local:{} graft:{}", NodeUtils.getNodeIdString(routingTable.getLocalNode().getId().toByteArray()), NodeUtils.getNodeIdString(graft.getKey().getKey())); + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + ControlMessage.Builder controlMessageBuilder = ControlMessage.newBuilder(); + + GraftMessage.Builder graftMessageBuilder = GraftMessage.newBuilder(); + graftMessageBuilder.addAllTopic(graft.getValue()); + graftMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + + List pruneTopic = toPrune.get(graft.getKey()); + + if (pruneTopic != null) { + PruneMessage.Builder pruneMessageBuilder = PruneMessage.newBuilder(); + pruneMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + pruneMessageBuilder.addAllTopic(pruneTopic); + toPrune.remove(graft.getKey()); + controlMessageBuilder.setPrune(pruneMessageBuilder); + } + + controlMessageBuilder.setGraft(graftMessageBuilder); + + topicMessageBuilder.setControl(controlMessageBuilder); + sendMessage(graft.getKey(), topicMessageBuilder); + } catch (Exception e) { + logger.error("send graft error:", e); + } + } + + for (Map.Entry> prune : toPrune.entrySet()) { + TopicMessage.Builder topicMessageBuilder = newTopicMessageBuilder(); + ControlMessage.Builder controlMessageBuilder = ControlMessage.newBuilder(); + + PruneMessage.Builder pruneMessageBuilder = PruneMessage.newBuilder(); + + pruneMessageBuilder.setNodeId(routingTable.getLocalNode().getId()); + pruneMessageBuilder.addAllTopic(prune.getValue()); + + + + controlMessageBuilder.setPrune(pruneMessageBuilder); + topicMessageBuilder.setControl(controlMessageBuilder); + sendMessage(prune.getKey(), topicMessageBuilder); + } + + }); + } + + + private void emitMessage(String topic, Set peers) { + logger.trace("emit peers:{}", peers.size()); + for (Bytes peer : peers) { + Set msgIds = messageIdCache.get(topic); + + logger.trace("emit message nodeid:{} msg nodeId size:{}", NodeUtils.getNodeIdString(peer.getKey()), msgIds.size()); + + Map unSendList = unSendMessage.getOrDefault(peer, new HashMap<>()); + IHaveMessage iHaveMessage = unSendList.getOrDefault(topic, IHaveMessage.newBuilder().build()); + iHaveMessage = iHaveMessage.toBuilder().setTopic(topic).addAllMessageId(msgIds).build(); + + unSendList.putIfAbsent(topic, iHaveMessage); + + unSendMessage.putIfAbsent(peer, unSendList); + + } + } + + + private TopicMessage.Builder newTopicMessageBuilder() { + + TopicMessage.Builder topicMessageBuilder = TopicMessage.newBuilder(); + topicMessageBuilder.setFromNodeId(routingTable.getLocalNode().getId()); + return topicMessageBuilder; + } + + + +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/PubSubService.java b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubService.java new file mode 100644 index 0000000..d177a78 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubService.java @@ -0,0 +1,29 @@ +package org.platon.p2p.pubsub; + +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author yangzhou + * @create 2018-07-24 17:56 + */ +@Component("pubSubService") +public class PubSubService { + + + + @Autowired + private PubSub pubSub; + + public void setPubSub(PubSub pubSub) { + this.pubSub = pubSub; + } + + @PlatonMessageType("TopicMessage") + public void sendMessage(TopicMessage msg, HeaderHelper header) { + pubSub.sendMessage(msg, header); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/PubSubSessionNotify.java b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubSessionNotify.java new file mode 100644 index 0000000..d857cb8 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/PubSubSessionNotify.java @@ -0,0 +1,46 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.ByteString; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.session.SessionNotify; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author yangzhou + * @create 2018-07-24 15:35 + */ +public class PubSubSessionNotify implements SessionNotify.SessionNotifyCallback{ + private static Logger logger = LoggerFactory.getLogger(PubSubSessionNotify.class); + + private PubSub pubSub; + + private NodeID localNodeId; + public PubSubSessionNotify(PubSub pubSub, NodeID localNodeId){ + this.pubSub = pubSub; + this.localNodeId = localNodeId; + } + + + @Override + public void create(ByteString remoteNodeId) { + + if (remoteNodeId.equals(localNodeId.getId())){ + return; + } + + + logger.debug("session on :{}", NodeUtils.getNodeIdString(remoteNodeId)); + pubSub.addPeer(remoteNodeId); + } + + @Override + public void close(ByteString remoteNodeId) { + if (remoteNodeId.equals(localNodeId.getId())){ + return; + } + logger.debug("session off :{}", NodeUtils.getNodeIdString(remoteNodeId)); + pubSub.removePeer(remoteNodeId); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/pubsub/TimeCache.java b/p2p/src/main/java/org/platon/p2p/pubsub/TimeCache.java new file mode 100644 index 0000000..3dd59c5 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/pubsub/TimeCache.java @@ -0,0 +1,55 @@ +package org.platon.p2p.pubsub; + +import org.apache.http.util.Asserts; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +/** + * @author yangzhou + * @create 2018-07-24 9:58 + */ +public class TimeCache { + private long span; + + private Map seen = new HashMap<>(); + private Queue queue = new LinkedList<>(); + + TimeCache(long span) { + this.span = span; + } + + public void add(String msg) { + seen.put(msg, System.currentTimeMillis()); + queue.offer(msg); + sweep(); + } + + public boolean has(String msg) { + return seen.containsKey(msg); + } + + public void sweep() { + Long current = System.currentTimeMillis(); + + while (true) { + String msg = queue.peek(); + if (msg == null) { + return; + } + Long time = seen.get(msg); + Asserts.check(time != null, "inconsistent cache state"); + if (current - time > span) { + queue.poll(); + seen.remove(msg); + } else { + return; + } + } + } +// public String dump() { +// return JSON.toJSONString(seen, true); +// } +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/KeyAlgorithm.java b/p2p/src/main/java/org/platon/p2p/redir/KeyAlgorithm.java new file mode 100644 index 0000000..888d47f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/KeyAlgorithm.java @@ -0,0 +1,51 @@ +package org.platon.p2p.redir; + +import java.math.BigInteger; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-23 11:04 + */ +public class KeyAlgorithm { + + private static Map keyFunction = new ConcurrentHashMap<>(); + + static { + add("hashrate", new HashRate()); + } + + + public static void add(String serviceType, KeyAlgorithmFunction func) { + keyFunction.put(serviceType, func); + } + + public static void remove(String serviceType) { + keyFunction.remove(serviceType); + } + + public static KeyAlgorithmFunction get(String serviceType) { + return keyFunction.get(serviceType); + } + + public interface KeyAlgorithmFunction { + BigInteger apply(String key, KeySelector selector); + } + + + + private static class HashRate implements KeyAlgorithmFunction{ + + @Override + public BigInteger apply(String key, KeySelector selector) { + BigInteger num = new BigInteger(key); + + if (num.compareTo(selector.getLowestKey()) < 0 || num.compareTo(selector.getHighestKey()) > 0) { + throw new RuntimeException("out range of key space"); + } + + return num; + } + } +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/KeySelector.java b/p2p/src/main/java/org/platon/p2p/redir/KeySelector.java new file mode 100644 index 0000000..934d883 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/KeySelector.java @@ -0,0 +1,220 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.ByteString; +import org.platon.p2p.proto.common.ResourceID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yangzhou + * @create 2018-04-28 11:16 + */ + +public class KeySelector { + + private static Logger logger = LoggerFactory.getLogger(KeySelector.class); + + + + private int branchingFactor; + private int startLevel; + private BigInteger lowestKey; + private BigInteger highestKey; + private String namespace; + private int level; + private KeyAlgorithm.KeyAlgorithmFunction keyAlgorithmFunction; + + + public static class Builder { + private int branchingFactor; + private int startLevel; + private BigInteger lowestKey; + private BigInteger highestKey; + private String namespace; + private int level; + private KeyAlgorithm.KeyAlgorithmFunction keyAlgorithmFunction; + + public Builder() { + } + + public Builder branchingFactor(int branchingFactor){ + this.branchingFactor = branchingFactor; + return this; + } + public Builder startLevel(int startLevel){ + this.startLevel = startLevel; + return this; + } + + public Builder lowestKey(BigInteger lowestKey){ + this.lowestKey = lowestKey; + return this; + } + + public Builder highestKey(BigInteger highestKey){ + this.highestKey = highestKey; + return this; + } + + public Builder namespace(String namespace){ + this.namespace = namespace; + return this; + } + + + public Builder level(int level){ + this.level = level; + return this; + } + + public Builder keyAlgorithmFunction(KeyAlgorithm.KeyAlgorithmFunction keyAlgorithmFunction){ + this.keyAlgorithmFunction = keyAlgorithmFunction; + return this; + } + KeySelector build(){ + return new KeySelector(this); + } + } + + + private KeySelector(Builder builder){ + this.branchingFactor = builder.branchingFactor; + this.startLevel = builder.startLevel; + this.lowestKey = builder.lowestKey; + this.highestKey = builder.highestKey; + this.namespace = builder.namespace; + this.level = builder.level; + this.keyAlgorithmFunction = builder.keyAlgorithmFunction; + } + + public int getBranchingFactor() { + return branchingFactor; + } + + public int getStartLevel() { + return startLevel; + } + + public BigInteger getLowestKey() { + return lowestKey; + } + + public BigInteger getHighestKey() { + return highestKey; + } + + public String getNamespace() { + return namespace; + } + + public KeyAlgorithm.KeyAlgorithmFunction getKeyAlgorithmFunction() { + return keyAlgorithmFunction; + } + + public int getLevel() { + return level; + } + + private ResourceID getResourceIDMock(byte[] key) { + MessageDigest mdTemp = null; + try { + mdTemp = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + logger.error("error:", e); + } + mdTemp.update(key); + return ResourceID.newBuilder().setId(ByteString.copyFrom(mdTemp.digest())).build(); + } + + public ResourceID getResourceID(Integer level, String key) { + + + BigInteger value = keyAlgorithmFunction.apply(key, this); + + if (lowestKey.compareTo(value) > 0 || highestKey.compareTo(value) < 0) { + logger.warn("get resource nodeId failed key:{} lowest key:{} highest key:{}", key.toString(), lowestKey.toString(), highestKey.toString()); + return null; + } + + BigInteger pos = value.subtract(lowestKey); + BigInteger keyspace = highestKey.subtract(lowestKey); + + + BigInteger step = keyspace.divide(BigInteger.valueOf((long) Math.pow(branchingFactor, level-1) * 2)); + BigInteger node = pos.divide(step).add(pos.mod(step).compareTo(BigInteger.valueOf(0)) == 0 ? BigInteger.valueOf(0) : BigInteger.valueOf(1)); + + + MessageDigest mdTemp = null; + try { + mdTemp = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + logger.error("error:", e); + } + + node = node.divide(BigInteger.valueOf(2)).add(node.mod(BigInteger.valueOf(2)).compareTo(BigInteger.valueOf(0)) == 0 ? BigInteger.valueOf(0) : BigInteger.valueOf(1)); + String nodeId = namespace + level.toString() + node.toString(); + + mdTemp.update(nodeId.getBytes()); + return ResourceID.newBuilder().setId(ByteString.copyFrom(mdTemp.digest())).build(); + + } + + public boolean isBoundary(Integer level, BigInteger key) { + + + key = key.abs(); + if (lowestKey.compareTo(key) > 0 || highestKey.compareTo(key) < 0) { + logger.warn("is boundary failed key:{} lowest key:{} highest key:{}", key.toString(), lowestKey.toString(), highestKey.toString()); + return false; + } + BigInteger pos = key.subtract(lowestKey); + BigInteger keyspace = highestKey.subtract(lowestKey); + + + BigInteger step = keyspace.divide(BigInteger.valueOf((long) Math.pow(branchingFactor, level-1) * 2)); + BigInteger node = pos.divide(step).add(pos.mod(step).compareTo(BigInteger.valueOf(0)) == 0 ? BigInteger.valueOf(0) : BigInteger.valueOf(1)); + + + + + if (key.compareTo(step.multiply(node.subtract(BigInteger.valueOf(1)))) == 0 + || key.compareTo(step.multiply(node).subtract(BigInteger.valueOf(1))) == 0 ){ + return true; + } + return false; + } + + public BigInteger generateKey(String key) { + return keyAlgorithmFunction.apply(key, this); + } + + public List getAllResourceID() { + return getAllResourceID(startLevel); + } + public List getAllResourceID(Integer level) { + List resourceIDS = new ArrayList<>(); + long levelSize = (long) Math.pow(branchingFactor, level-1); + for (long i = 0; i < levelSize; i++) { + MessageDigest mdTemp = null; + try { + mdTemp = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + logger.error("error:", e); + } + + String nodeid = namespace + level.toString() + Long.toString(i); + + mdTemp.update(nodeid.getBytes()); + ResourceID r = ResourceID.newBuilder().setId(ByteString.copyFrom(mdTemp.digest())).build(); + resourceIDS.add(r); + } + return resourceIDS; + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/KeySelectorFactory.java b/p2p/src/main/java/org/platon/p2p/redir/KeySelectorFactory.java new file mode 100644 index 0000000..d041809 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/KeySelectorFactory.java @@ -0,0 +1,31 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.common.ReDiRConfig; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author yangzhou + * @create 2018-07-25 19:48 + */ +public class KeySelectorFactory { + + private static Map selectorMap = new HashMap<>(); + public static KeySelector get(String serviceType) { + KeySelector selector = selectorMap.get(serviceType); + if (selector != null) { + return selector; + } + + selector = new KeySelector.Builder(). + branchingFactor(ReDiRConfig.getInstance().getBranchingFactor(serviceType)). + namespace(serviceType). + level(ReDiRConfig.getInstance().getLevel(serviceType)). + lowestKey(ReDiRConfig.getInstance().getLowestKey(serviceType)). + highestKey(ReDiRConfig.getInstance().getHighestKey(serviceType)). + startLevel(ReDiRConfig.getInstance().getStartLevel(serviceType)). + keyAlgorithmFunction(KeyAlgorithm.get(ReDiRConfig.getInstance().getAlgorithm(serviceType))).build(); + return selector; + } +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ReDiR.java b/p2p/src/main/java/org/platon/p2p/redir/ReDiR.java new file mode 100644 index 0000000..5d3c29f --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ReDiR.java @@ -0,0 +1,307 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.Message; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.db.DB; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.redir.*; +import org.platon.p2p.pubsub.PubSub; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + + +/** + * @author yangzhou + * @create 2018-04-28 11:15 + */ +@Component("reDiR") +public class ReDiR { + + private static Logger logger = LoggerFactory.getLogger(ReDiR.class); + + + + @Autowired + PubSub pubSub; + @Autowired + DB db; + @Autowired + MessageRouter messageRouter; + + public void setPubSub(PubSub pubSub) { + this.pubSub = pubSub; + } + + public void setDb(DB db) { + this.db = db; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + public void registerService(ServiceEntry entry, KeySelector selector) { + + + ReDiRMessage.Builder redirMessageBuilder = ReDiRMessage.newBuilder(); + + ResourceID resourceId = selector.getResourceID(selector.getStartLevel(), entry.getSourceKey()); + redirMessageBuilder.setEntry(entry); + redirMessageBuilder.setResourceId(resourceId); + + ReDiRMessage reDiRMessage = redirMessageBuilder.build(); + + ServiceDiscoveryUtil.storeLocalMessage(db, pubSub, reDiRMessage, System.currentTimeMillis()); + + logger.debug("registerService resourceId:{}, message:{}", resourceId, reDiRMessage.toString()); + + + redirMessageBuilder.setEntry(entry); + + + + CompletableFuture fut = messageRouter.sendRequest(reDiRMessage, + Collections.singletonList(NodeUtils.toRoutableID(resourceId)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg -> { + addDownRegister(futMsg, reDiRMessage, selector.getStartLevel(), selector); + }, NodeContext.executor).exceptionally(throwable -> { + logger.warn(throwable.getMessage()); + return null; + }); + + fut.thenAcceptAsync(futMsg -> { + addUpRegister(futMsg, reDiRMessage, selector.getStartLevel(), selector); + }, NodeContext.executor).exceptionally(throwable -> { + logger.warn(throwable.getMessage()); + return null; + }); + } + + + + + + private void addUpRegister(Message msg, ReDiRMessage send, Integer level, KeySelector selector) { + logger.trace("current level:{}", level); + + + if (level == 1) { + return; + } + + logger.trace("up register level:{}", level-1); + ServiceEntry entry = send.getEntry(); + + List sortedList = generateSortedKey(((ReDiRRespMessage) msg).getKeyList(), selector); + BigInteger me = selector.generateKey(entry.getSourceKey()); + + ReDiRMessage.Builder redirMessageBuilder = ReDiRMessage.newBuilder(); + redirMessageBuilder.setEntry(entry); + + + + if (sortedList.isEmpty() + || (sortedList.get(0).compareTo(me) >= 0 + || sortedList.get(sortedList.size() - 1).compareTo(me) <= 0) + || (level > 0 && selector.isBoundary(level, me))) { + + logger.trace("up register level:{}", level -1); + try { + + ResourceID resourceId = selector.getResourceID(level - 1, entry.getSourceKey()); + + redirMessageBuilder.setResourceId(resourceId); + logger.trace("up register resourceid:{}", NodeUtils.getNodeIdString(resourceId.getId())); + + final ReDiRMessage reDiRMessage = redirMessageBuilder.build(); + + CompletableFuture fut = messageRouter.sendRequest(reDiRMessage, + Collections.singletonList(NodeUtils.toRoutableID(resourceId)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg -> { + addUpRegister(futMsg, reDiRMessage, level-1, selector); + }, NodeContext.executor); + + }catch (Exception e) { + logger.warn("error:", e); + } + } else { + logger.trace("isn't need up register level:{}", level-1); + } + + } + + + + private void addDownRegister(Message msg, ReDiRMessage send, Integer level, KeySelector selector) { + ServiceEntry entry = send.getEntry(); + + logger.trace("down register level:{} total level:{}", level, selector.getLevel()); + + if (level == selector.getLevel()) { + return; + } + + List sortedList = generateSortedKey(((ReDiRRespMessage) msg).getKeyList(), selector); + BigInteger me = selector.generateKey(entry.getSourceKey()); + + logger.trace("sortedList.size:{} get(0): me:", sortedList.size()); + + ReDiRMessage.Builder redirMessageBuilder = ReDiRMessage.newBuilder(); + + + + if ((sortedList.size() == 1 && sortedList.get(0).compareTo(me) == 0) + || (selector.isBoundary(level + 1, me))) { + + logger.trace("down register level:{}", level + 1); + ResourceID resourceId = selector.getResourceID(level + 1, entry.getSourceKey()); + + redirMessageBuilder.setResourceId(resourceId); + + ReDiRMessage reDiRMessage = redirMessageBuilder.build(); + CompletableFuture fut = messageRouter.sendRequest(reDiRMessage, + Collections.singletonList(NodeUtils.toRoutableID(resourceId)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg -> { + addDownRegister(futMsg, reDiRMessage, level+1, selector); + }, NodeContext.executor); + } else { + logger.trace("isn't down register level:{}", level + 1); + } + } + + + private List generateSortedKey(List keyList, KeySelector selector) { + List sortedList = new ArrayList<>(); + if (keyList == null) { + return sortedList; + } + + for (T entry : keyList) { + + if (entry instanceof String) { + sortedList.add(selector.generateKey((String) entry)); + } else if (entry instanceof ServiceEntry) { + sortedList.add(selector.generateKey(((ServiceEntry) entry).getSourceKey())); + } else { + throw new RuntimeException("unknown keyList type :" + entry.getClass().getName()); + } + } + Collections.sort(sortedList); + return sortedList; + } + + + + + + public CompletableFuture> findService(String key, KeySelector selector) { + logger.debug("find service key:{}", selector.generateKey(key)); + CompletableFuture> res = new CompletableFuture>(); + + ResourceID resourceId = selector.getResourceID(selector.getStartLevel(), key); + + ReDiRFindMessage.Builder reDiRFindMessageBuilder = ReDiRFindMessage.newBuilder(); + reDiRFindMessageBuilder.setResourceId(resourceId); + reDiRFindMessageBuilder.setFindKey(key); + + ReDiRFindMessage reDiRFindMessage = reDiRFindMessageBuilder.build(); + + + CompletableFuture fut = messageRouter.sendRequest(reDiRFindMessage, + Collections.singletonList(NodeUtils.toRoutableID(resourceId)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg -> { + findService(futMsg, reDiRFindMessage, selector.getStartLevel(), res, selector); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + + return res; + } + + + + + private void findService(Message msg, ReDiRFindMessage send, Integer level, CompletableFuture> res, KeySelector selector) { + String key = send.getFindKey(); + logger.debug("find service key:{} level:{}", selector.generateKey(key), level); + + ReDiRFindRespMessage serviceMsg = (ReDiRFindRespMessage) msg; + + + + if (serviceMsg.getEntryList() != null && !serviceMsg.getEntryList().isEmpty()) { + logger.debug("find key:{}", key); + res.complete(serviceMsg.getEntryList()); + return; + } + + + if (serviceMsg.getEntryList() == null || serviceMsg.getKeyList().isEmpty()) { + logger.warn("not found resource in level:{} key:{}", level, selector.generateKey(key)); + res.completeExceptionally(new ReDiRExecption("not found resource")); + return; + } + + List keyList = generateSortedKey(serviceMsg.getKeyList(), selector); + BigInteger me = selector.generateKey(key); + + + + if (keyList.isEmpty() || (keyList.get(keyList.size() - 1).compareTo(me) < 0)) { + if (level > selector.getStartLevel()) { + logger.warn("not found resource in level:{} key:{}", level, key.toString()); + res.completeExceptionally(new ReDiRExecption("not found resource")); + return; + } + level -= 1; + + } else if (selector.isBoundary(level + 1, me)) { + logger.warn("not found resource in level:{} key:{}", level + 1, key.toString()); + res.completeExceptionally(new ReDiRExecption("not found resource")); + return; + } else { + if (level >= selector.getLevel()) { + logger.warn("not found resource key:{} level:{}", key.toString(), level); + res.completeExceptionally(new ReDiRExecption("not found resource")); + return; + } + level += 1; + } + + ResourceID resourceId = selector.getResourceID(level, key); + + CompletableFuture fut = messageRouter.sendRequest(send, + Collections.singletonList(NodeUtils.toRoutableID(resourceId)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + final Integer currentLevel = level; + fut.thenAcceptAsync(futMsg -> { + findService(futMsg, send, currentLevel, res, selector); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ReDiRExecption.java b/p2p/src/main/java/org/platon/p2p/redir/ReDiRExecption.java new file mode 100644 index 0000000..57e4a3d --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ReDiRExecption.java @@ -0,0 +1,10 @@ +package org.platon.p2p.redir; + + + +public class ReDiRExecption extends Exception { + public ReDiRExecption(final String msg) { + super(msg, null); + } +} + diff --git a/p2p/src/main/java/org/platon/p2p/redir/ReDiRForwardMessageHook.java b/p2p/src/main/java/org/platon/p2p/redir/ReDiRForwardMessageHook.java new file mode 100644 index 0000000..4e894f8 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ReDiRForwardMessageHook.java @@ -0,0 +1,49 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import org.platon.p2p.ForwardMessageHook; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.platon.Header; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author yangzhou + * @create 2018-07-26 18:57 + */ +public class ReDiRForwardMessageHook implements ForwardMessageHook.ForwardMessageCallback { + private static Logger logger = LoggerFactory.getLogger(ReDiRForwardMessageHook.class); + + private ServiceDiscoveryManager serviceDiscoveryManager = null; + + ReDiRForwardMessageHook(ServiceDiscoveryManager serviceDiscoveryManager) { + this.serviceDiscoveryManager = serviceDiscoveryManager; + } + + @Override + public List nextHops(Header header, Any any) { + logger.trace("receive nexthops message header:{}", header); + List nextHops = new ArrayList<>(); + + Set listPeers = serviceDiscoveryManager.pubSub.listPeers(NodeUtils.getNodeIdString(HeaderHelper.build(header).senderId().getId())); + + if (listPeers == null || listPeers.isEmpty()) { + return null; + } + + listPeers.forEach(node -> { + nextHops.add(NodeID.newBuilder().setId(ByteString.copyFrom(node.getKey())).build()); + }); + + return nextHops; + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ReDiRMessageHook.java b/p2p/src/main/java/org/platon/p2p/redir/ReDiRMessageHook.java new file mode 100644 index 0000000..25abda7 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ReDiRMessageHook.java @@ -0,0 +1,78 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.MessageHook; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.proto.redir.ReDiRFindMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author yangzhou + * @create 2018-08-06 11:10 + */ +public class ReDiRMessageHook implements MessageHook.MessageCallback { + private static Logger logger = LoggerFactory.getLogger(ReDiRMessageHook.class); + + private ServiceDiscoveryManager serviceDiscoveryManager = null; + ReDiRMessageHook(ServiceDiscoveryManager serviceDiscoveryManager){ + this.serviceDiscoveryManager = serviceDiscoveryManager; + } + + @Override + public boolean isNeedProcess(PlatonMessage request) { + + logger.trace("ReDiRMessageHook process"); + + try { + + List destIdList = request.getHeader().getDestList(); + + + String topic = NodeUtils.getNodeIdString(destIdList.get(0).getId()); + + + + + + if (serviceDiscoveryManager.pubSub.isSubscribe(topic)) { + + if (ProtoBufHelper.getTypeNameFromTypeUrl( + request.getBody().getData().getTypeUrl()) + .compareTo(ProtoBufHelper.getFullName(ReDiRFindMessage.class)) == 0) { + return true; + } + + Set peers = new HashSet<>(); + Set topicPeers = serviceDiscoveryManager.pubSub.listPeers(topic); + if (topicPeers != null) { + peers.addAll(topicPeers); + } + + Bytes localBytes = Bytes.valueOf(serviceDiscoveryManager.routingTable.getLocalNode().getId()); + peers.add(localBytes); + + + if (NodeUtils.closestNode(Bytes.valueOf(destIdList.get(0).getId()), peers).compareTo(localBytes) == 0) { + logger.trace("need local process"); + return true; + } + } + + logger.trace("doesn't need local process, is not subscribe topic:{}", topic); + return false; + }catch (Exception e) { + logger.error("error:", e); + return false; + } + + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscovery.java b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscovery.java new file mode 100644 index 0000000..3488326 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscovery.java @@ -0,0 +1,46 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.proto.redir.ReDiRFindMessage; +import org.platon.p2p.proto.redir.ReDiRFindRespMessage; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.proto.redir.ReDiRRespMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author yangzhou + * @create 2018-04-27 11:53 + */ +@Component("serviceDiscovery") +public class ServiceDiscovery { + + @Autowired + ServiceDiscoveryManager serviceDiscoveryManager; + + public void setServiceDiscoveryManager(ServiceDiscoveryManager serviceDiscoveryManager) { + this.serviceDiscoveryManager = serviceDiscoveryManager; + } + + @PlatonMessageType("ReDiRMessage") + public void publish(ReDiRMessage msg, HeaderHelper header) { + serviceDiscoveryManager.publish(msg, header); + } + + @PlatonMessageType("ReDiRRespMessage") + public void publishResp(ReDiRRespMessage msg, HeaderHelper header) { + serviceDiscoveryManager.publishResp(msg, header); + } + + @PlatonMessageType("ReDiRFindMessage") + public void discovery(ReDiRFindMessage msg, HeaderHelper header) { + serviceDiscoveryManager.discovery(msg, header); + } + + @PlatonMessageType("ReDiRFindRespMessage") + public void discoveryResp(ReDiRFindRespMessage msg, HeaderHelper header) { + serviceDiscoveryManager.discoveryResp(msg, header); + } + +} \ No newline at end of file diff --git a/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryManager.java b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryManager.java new file mode 100644 index 0000000..754d66b --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryManager.java @@ -0,0 +1,285 @@ +package org.platon.p2p.redir; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.tuple.Pair; +import org.platon.common.cache.DelayCache; +import org.platon.p2p.ForwardMessageHook; +import org.platon.p2p.MessageHook; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBException; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.redir.*; +import org.platon.p2p.proto.storage.StoredData; +import org.platon.p2p.pubsub.PubSub; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author yangzhou + * @create 2018-07-25 17:09 + */ +@Component("serviceDiscoveryManager") +public class ServiceDiscoveryManager { + + private static Logger logger = LoggerFactory.getLogger(ServiceDiscoveryManager.class); + + private static final int rePublishDuration = 100000; + + + private ServiceDiscoverySubCallback subCallback = new ServiceDiscoverySubCallback(); + + + private DelayCache repeatEntry = new DelayCache<>(); + + @Autowired + PubSub pubSub; + + @Autowired + DB db; + + @Autowired + MessageRouter messageRouter; + + @Autowired + ReDiR reDiR; + + @Autowired + RoutingTable routingTable; + + ReDiRForwardMessageHook reDiRForwardMessageHook = null; + ReDiRMessageHook reDiRMessageHook = null; + + public ServiceDiscoveryManager(){ + reDiRForwardMessageHook = new ReDiRForwardMessageHook(this); + reDiRMessageHook = new ReDiRMessageHook(this); + + ForwardMessageHook.add("ServiceDiscovery", reDiRForwardMessageHook); + MessageHook.add("ServiceDiscovery", reDiRMessageHook); + } + + + public void setPubSub(PubSub pubSub) { + this.pubSub = pubSub; + } + + public void setDb(DB db) { + this.db = db; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + + + public void publish(ServiceEntry entry, boolean repeat){ + + KeySelector selector = KeySelectorFactory.get(entry.getServiceType()); + + + ResourceID resourceId = selector.getResourceID(selector.getStartLevel(), entry.getSourceKey()); + if (resourceId == null) { + logger.error("resource nodeId is null"); + throw new RuntimeException("resource nodeId is null please check sourceKey"); + } + + + if (repeat) { + repeatEntry.put(serviceEntryId(entry), entry, rePublishDuration, TimeUnit.MILLISECONDS); + } + + + pubSub.subscribe(NodeUtils.getNodeIdString(resourceId.getId()), PubSub.class.getName(), subCallback); + + + reDiR.registerService(entry, selector); + + } + + public CompletableFuture> find(String serviceType, String key) { + KeySelector selector = KeySelectorFactory.get(serviceType); + return reDiR.findService(key, selector); + } + + + + + private String serviceEntryId(ServiceEntry entry) { + + return entry.getFrom() + entry.getServiceType(); + } + + + private class ServiceDiscoveryManagerDelayCacheCallback implements DelayCache.TimeoutCallbackFunction { + + @Override + public void timeout(Object key, Object value) { + + + + + publish((ServiceEntry) value, true); + + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public void publish(ReDiRMessage msg, HeaderHelper header){ + logger.debug("receive redir resource nodeId:{}",NodeUtils.getNodeIdString(msg.getResourceId().getId())); + + + long now = System.currentTimeMillis(); + + + + ServiceDiscoveryUtil.storeLocalMessage(db, pubSub, msg, now); + + + ReDiRRespMessage.Builder reDiRRespMessageBuilder = ReDiRRespMessage.newBuilder(); + try { + + List> data = db.hgetAll(msg.getResourceId().getId().toByteArray()); + Set keys = new HashSet<>(); + for (Pair e : data) { + StoredData store = ProtoBufHelper.decodeIceData(e.getValue(), StoredData.parser()); + + ServiceEntry entry = ProtoBufHelper.decodeIceData(store.getValue(), ServiceEntry.parser()); + if (store == null || store.getStorageTime() + store.getLifeTime() < now) { + + + logger.trace("del key:{}", Hex.encodeHexString(e.getKey())); + db.hdel(msg.getResourceId().getId().toByteArray(), entry.getFrom().getBytes()); + } else { + logger.trace("publish return key:" + Hex.encodeHexString(e.getKey())); + keys.add(entry.getSourceKey()); + } + } + reDiRRespMessageBuilder.addAllKey(keys); + } catch (DBException e) { + logger.error("error:", e); + } + + messageRouter.sendResponse(reDiRRespMessageBuilder.build(), + header.txId(), + header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + + } + + + + + + + + public void subscribe(String topic, byte[] data) { + logger.trace("subscribe :{} storage local", topic); + + StoredData store = ProtoBufHelper.decodeIceData(data, StoredData.parser()); + ServiceEntry entry = ProtoBufHelper.decodeIceData(store.getValue(), ServiceEntry.parser()); + try { + db.hset(NodeUtils.getNodeIdBytes(topic), entry.getFrom().getBytes(), data); + } catch (DBException e) { + logger.error("error:", e); + } + } + + + + + + + public void publishResp(ReDiRRespMessage msg, HeaderHelper header){ + messageRouter.handleResponse(header.txId(), msg); + } + + + public void discovery(ReDiRFindMessage msg, HeaderHelper header) { + + ReDiRFindRespMessage.Builder reDiRFindRespMessageBuilder = ReDiRFindRespMessage.newBuilder(); + List keyList = new ArrayList<>(); + List entryList = new ArrayList<>(); + try { + logger.trace("hgetall:" +NodeUtils.getNodeIdString(msg.getResourceId().getId())); + List> data = db.hgetAll(msg.getResourceId().getId().toByteArray()); + long now = System.currentTimeMillis(); + + for (Pair m : data) { + + StoredData store = ProtoBufHelper.decodeIceData(m.getValue(), StoredData.parser()); + ServiceEntry entry = ProtoBufHelper.decodeIceData(store.getValue(), ServiceEntry.parser()); + if (store.getStorageTime() + store.getLifeTime() < now) { + db.hdel(msg.getResourceId().getId().toByteArray(), entry.getFrom().getBytes()); + } else { + if (entry.getSourceKey().equals(msg.getFindKey())) { + entryList.add(entry); + } else { + keyList.add(entry.getSourceKey()); + } + } + } + + reDiRFindRespMessageBuilder.setResourceId(msg.getResourceId()); + if (entryList.isEmpty()) { + reDiRFindRespMessageBuilder.addAllKey(keyList); + } else { + reDiRFindRespMessageBuilder.addAllEntry(entryList); + } + + } catch (DBException e) { + logger.error("error:", e); + } + + messageRouter.sendResponse(reDiRFindRespMessageBuilder.build(), + header.txId(), + header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + + public void discoveryResp(ReDiRFindRespMessage msg, HeaderHelper header){ + logger.debug("discover resp:{}", msg.toString()); + messageRouter.handleResponse(header.txId(), msg); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoverySubCallback.java b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoverySubCallback.java new file mode 100644 index 0000000..cfab7c7 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoverySubCallback.java @@ -0,0 +1,21 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.pubsub.PubSub; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author yangzhou + * @create 2018-07-25 19:24 + */ +public class ServiceDiscoverySubCallback implements PubSub.SubscribeCallback { + private static Logger logger = LoggerFactory.getLogger(ServiceDiscoverySubCallback.class); + + @Override + public void subscribe(String topic, byte[] data) { + + logger.trace("receive new data:{}", topic); + + + } +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryUtil.java b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryUtil.java new file mode 100644 index 0000000..ee63daa --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/ServiceDiscoveryUtil.java @@ -0,0 +1,54 @@ +package org.platon.p2p.redir; + +import org.apache.commons.codec.binary.Hex; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBException; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.proto.storage.StoredData; +import org.platon.p2p.pubsub.PubSub; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + + + +public class ServiceDiscoveryUtil { + + private static org.slf4j.Logger logger = LoggerFactory.getLogger(ServiceDiscoveryUtil.class); + + private static final long LIFETIME = 100000; + + + + + public static void storeLocalMessage(DB db, PubSub pubSub, ReDiRMessage msg, long now) { + + + StoredData.Builder storedDataBuilder = StoredData.newBuilder(); + + + + + storedDataBuilder.setKey(msg.getResourceId().getId()); + storedDataBuilder.setValue(ProtoBufHelper.encodeIceData(msg.getEntry())); + storedDataBuilder.setStorageTime(now); + storedDataBuilder.setLifeTime(LIFETIME); + + + byte[] storeBytes = ProtoBufHelper.encodeIceData(storedDataBuilder.build()).toByteArray(); + + try { + db.hset(msg.getResourceId().getId().toByteArray(), msg.getEntry().getFrom().getBytes(), storeBytes); + } catch (DBException e) { + logger.error("error:", e); + } + + + CompletableFuture.runAsync(()-> { + pubSub.publish(Hex.encodeHexString(msg.getResourceId().getId().toByteArray()), storeBytes); + }); + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/redir/TurnService.java b/p2p/src/main/java/org/platon/p2p/redir/TurnService.java new file mode 100644 index 0000000..7fa7e9d --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/redir/TurnService.java @@ -0,0 +1,103 @@ +package org.platon.p2p.redir; + +import org.springframework.stereotype.Component; + +/** + * @author yangzhou + * @create 2018-05-11 17:36 + */ +@Component("turnService") +public class TurnService extends ReDiR{ + +// +// private static String SERVICENAME = "turn"; +// +// public TurnService() { +// KeySelector selector = new KeySelector(); +// +// selector.setBranchingFactor(ReDiRConfig.getInstance().getBranchingFactor(SERVICENAME)); +// selector.setStartLevel(ReDiRConfig.getInstance().getStartLevel(SERVICENAME)); +// selector.setLowestKey(ReDiRConfig.getInstance().getLowestKey(SERVICENAME)); +// selector.setHighestKey(ReDiRConfig.getInstance().getHighestKey(SERVICENAME)); +// selector.setNamespace(SERVICENAME); +// selector.setLevel(ReDiRConfig.getInstance().getLevel(SERVICENAME)); +// setSelector(selector); +// } +// +// static private byte[] encodeValue(NodeID nodeId) { +// byte[] encode = new byte[1 + nodeId.nodeId.length + nodeId.getEndpoint().length()]; +// ByteBuffer buffer = ByteBuffer.wrap(encode); +// buffer.addSessionFuture((byte)nodeId.nodeId.length); +// buffer.addSessionFuture(nodeId.getId()); +// buffer.addSessionFuture(nodeId.getEndpoint().getBytes()); +// +// return encode; +// } +// +// static private NodeID decodeValue(byte[] decode) { +// NodeID nodeID = new NodeID(); +// ByteBuffer buffer = ByteBuffer.wrap(decode); +// byte idLen = buffer.get(); +// byte[] nodeId = new byte[idLen]; +// buffer.get(nodeId); +// nodeID.setId(nodeId); +// if (buffer.hasRemaining()){ +// byte[] endpoint = new byte[buffer.remaining()]; +// buffer.get(endpoint); +// nodeID.setEndpoint(new String(endpoint)); +// return nodeID; +// } +// return null; +// } +// +// public void registerService(NodeID nodeID) throws ExecutionException, InterruptedException { +// if (nodeID.getEndpoint().isEmpty()) { +// return; +// } +// +// ReDiRKindData registerData = new ReDiRKindData(); +// registerData.setValue(encodeValue(nodeID)); +// registerData.setId(nodeID); +// registerData.setKey(nodeID.nodeId); +// registerService(registerData); +// } +// +// public CompletableFuture findService(NodeID nodeId) { +// final CompletableFuture node = new CompletableFuture<>(); +// CompletableFuture> fut = findService(new BigInteger(nodeId.getId())); +// +// fut.thenAcceptAsync(futMsg -> { +// NodeID nodeID = decodeValue(futMsg.get(0).getValue()); +// if (nodeID == null) { +// node.completeExceptionally(new Exception("node nodeId parse failed")); +// } else { +// node.complete(nodeID); +// } +// +// }, NodeContext.executor).exceptionally(throwable->{ +// node.completeExceptionally(throwable); +// return null; +// }); +// +// return node; +// } +// +// public CompletableFuture findOne() { +// final CompletableFuture futNode = new CompletableFuture<>(); +// CompletableFuture> fut = findAnyoneService(); +// +// fut.thenAcceptAsync(pair -> { +// NodeID nodeID = decodeValue(pair.getValue()); +// if (nodeID == null) { +// futNode.completeExceptionally(new Exception("node nodeId parse failed")); +// } else { +// futNode.complete(nodeID); +// } +// }, NodeContext.executor).exceptionally(throwable->{ +// futNode.completeExceptionally(throwable); +// return null; +// }); +// return futNode; +// } + +} diff --git a/p2p/src/main/java/org/platon/p2p/router/MessageRouter.java b/p2p/src/main/java/org/platon/p2p/router/MessageRouter.java new file mode 100644 index 0000000..f0917ed --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/router/MessageRouter.java @@ -0,0 +1,328 @@ +package org.platon.p2p.router; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.platon.p2p.ForwardMessageHook; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.common.PlatonMessageHelper; +import org.platon.p2p.plugins.TopologyPlugin; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.session.Session; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + + +/** + * @author yangzhou + * @create 2018-04-26 16:55 + */ +@Component("messageRouter") +public class MessageRouter { + + private static Logger logger = LoggerFactory.getLogger(MessageRouter.class); + + @Autowired + private TopologyPlugin topologyPlugin; + + @Autowired + private SessionManager sessionManager; + + @Autowired + private RequestManager requestManager; + + public void setRequestManager(RequestManager requestManager) { + this.requestManager = requestManager; + } + + public void setTopologyPlugin(TopologyPlugin topologyPlugin) { + this.topologyPlugin = topologyPlugin; + } + + + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + + public enum ForwardingOptionType { + + UNKNOWN_OPTION((byte)0), + + + DIRECT_CONNECTION((byte)1), + + FORWARD_CONNECTION((byte)2), + + + BROADCAST_CONNECTION((byte)3); + + final byte code; + private ForwardingOptionType(byte code) { + this.code = code; + } + + public static ForwardingOptionType valueOf(byte code) { + for (ForwardingOptionType t : EnumSet.allOf(ForwardingOptionType.class)) { + if (t.code == code) { + return t; + } + } + return UNKNOWN_OPTION; + } + } + + + public CompletableFuture sendRequest(Message msg, NodeID dest, ForwardingOptionType type, boolean isReturn) { + List destList = new ArrayList<>(); + RoutableID routableID = RoutableID.newBuilder().setId(dest.getId()).setType(RoutableID.DestinationType.NODEIDTYPE).build(); + destList.add(routableID); + return this.sendRequest(msg, destList, type, isReturn); + } + + public CompletableFuture sendRequest(Message msg, List dest, ForwardingOptionType type, boolean isReturn) { + + String transactionID = UUID.randomUUID().toString(); + + + + + Header header = Header.newBuilder().setTxId(transactionID) + .addVia(topologyPlugin.getRoutingTable().getLocalNode()) + .addAllDest(dest) + .setTtl(30) + .setMsgType(msg.getClass().getSimpleName()) + .build(); + + + if (isReturn) { + final CompletableFuture msgFut = new CompletableFuture(); + requestManager.put(transactionID, msgFut); + sendMessage(msg, header, dest, type); + + return msgFut; + } + sendMessage(msg, header, dest, type); + + + + return null; + } + + public void sendResponse(Message msg, String transactionID, List dest, ForwardingOptionType type) { + Header header = Header.newBuilder().setTxId(transactionID) + .addVia(topologyPlugin.getRoutingTable().getLocalNode()) + .addAllDest(dest) + .setTtl(30) + .setMsgType(msg.getClass().getSimpleName()) + .build(); + + sendMessage(msg, header, dest, type); + } + + private class CreateSessionConsumer implements Consumer { + private PlatonMessage platonMessage; + private RoutableID origDestNode; + private ForwardingOptionType type; + private AtomicInteger counter; + + public CreateSessionConsumer(PlatonMessage platonMessage, RoutableID origDestNode, ForwardingOptionType type, int counter) { + this.platonMessage = platonMessage; + this.origDestNode = origDestNode; + this.type = type; + this.counter = new AtomicInteger(counter); + } + + @Override + public void accept(Session session) { + + logger.debug("开始消费sessionFuture回调..."); + + if (type == ForwardingOptionType.DIRECT_CONNECTION) { + if (session == null) { + logger.warn("Cannot connect to dest node, fail to send message to it directly."); + } else { + sendMessage(session, platonMessage); + } + } else if (type == ForwardingOptionType.FORWARD_CONNECTION) { + int count = counter.decrementAndGet(); + if (session == null) { + if (count == 0) { + + topologyPlugin.query(origDestNode); + + session = sessionManager.getSessionRandom(); + sendMessage(session, platonMessage); + } + } else { + sendMessage(session, platonMessage); + } + } + } + } + + + private void sendMessage(Message msg, Header header, List dest, ForwardingOptionType type){ + + Any any = Any.pack(msg); + PlatonMessage platonMessage = PlatonMessage.newBuilder().setHeader(header).setBody(org.platon.p2p.proto.platon.Body.newBuilder().setData(any)).build(); + + String txId = header.getTxId(); + if (type == ForwardingOptionType.DIRECT_CONNECTION) { + logger.debug("DIRECT_CONNECTION message:{}", platonMessage); + assert (dest.size() == 1); + for (RoutableID routableID : dest) { + if (routableID.getType() == RoutableID.DestinationType.NODEIDTYPE) { + logger.debug("DIRECT_CONNECTION:Get session from Node:{}", CodecUtils.toHexString(routableID.getId())); + + CompletableFuture sessionFut = sessionManager.getSession(routableID.getId(), true); + + sessionFut.thenAcceptAsync( + new CreateSessionConsumer(platonMessage, routableID, ForwardingOptionType.DIRECT_CONNECTION, 1), + NodeContext.executor).exceptionally(throwable -> { + handleException(txId, throwable); + return null; + }); + } + } + } else if (type == ForwardingOptionType.FORWARD_CONNECTION || type == ForwardingOptionType.BROADCAST_CONNECTION) { + Asserts.check(!dest.isEmpty(), "Destination list is empty"); + RoutableID destNode = dest.get(0); + List nextNodes = null; + if(type == ForwardingOptionType.FORWARD_CONNECTION) { + logger.debug("FORWARD_CONNECTION message:{}", platonMessage); + nextNodes = topologyPlugin.getRoutingTable().getNextHops(destNode); + }else{ + logger.debug("BROADCAST_CONNECTION message:{}", platonMessage); + nextNodes = topologyPlugin.getBroadCastNode(destNode); + } + logger.debug("nextNodes:::::::::{}", nextNodes.size() ); + if(nextNodes!=null && nextNodes.size()>0){ + Consumer consumer = new CreateSessionConsumer(platonMessage, destNode, ForwardingOptionType.FORWARD_CONNECTION, nextNodes.size()); + for (NodeID next : nextNodes) { + if(next.getId().equals(NodeContext.localNodeId)){ + + continue; + } + + CompletableFuture sessionFut = sessionManager.getSession(next.getId(), false); + + sessionFut.thenAcceptAsync(consumer, NodeContext.executor) + .exceptionally(throwable -> { + + + logger.error("Exception", throwable); + return null; + }); + + } + } + } else { + return; + } + } + + + + public void forwardPlatonMessage(Header header, Any any, ByteString localNodeId){ + + header = PlatonMessageHelper.renewHeader(header, localNodeId); + + PlatonMessage platonMessage = PlatonMessage.newBuilder().setHeader(header).setBody(org.platon.p2p.proto.platon.Body.newBuilder().setData(any)).build(); + + + + List nextHops = ForwardMessageHook.nextHops(header, any); + if (nextHops == null || nextHops.isEmpty()) { + nextHops = topologyPlugin.getRoutingTable().getNextHops(header.getDest(0)); + } + + logger.debug("forward message:{} to next hops:{}", platonMessage, nextHops); + + AtomicInteger counter = new AtomicInteger(nextHops.size()); + for (NodeID nodeId : nextHops) { + + if (PlatonMessageHelper.viaed(header.getViaList(), localNodeId)) { + continue; + } + sendMessage(nodeId, platonMessage, false); + } + } + + + private void sendMessage(NodeID nodeId, PlatonMessage platonMessage, boolean needAttach){ + logger.debug("send message:{} to node:={}", platonMessage, nodeId); + CompletableFuture getSessionFuture = sessionManager.getSession(nodeId.getId(), needAttach); + getSessionFuture.thenAcceptAsync(session -> { + if (session != null) { + sendMessage(session, platonMessage); + }else{ + + + + + + } + }, NodeContext.executor); + } + + private void sendMessage(Session session, PlatonMessage platonMessage){ + if(session!=null && session.getConnection()!=null && session.getConnection().isActive()){ + logger.debug("session:{}", session); + session.getConnection().writeAndFlush(platonMessage); + } + } + + + + + public void handleResponse(String id, Message msg) { + requestManager.handleResponse(id, msg); + } + + public void handleException(String id, Throwable throwable) { + requestManager.handleException(id, throwable); + } + + + + + + + + + + + + + + + + + + + + + + + + + + +} diff --git a/p2p/src/main/java/org/platon/p2p/router/RequestManager.java b/p2p/src/main/java/org/platon/p2p/router/RequestManager.java new file mode 100644 index 0000000..fe9746e --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/router/RequestManager.java @@ -0,0 +1,77 @@ +package org.platon.p2p.router; + +import com.google.protobuf.Message; +import org.platon.common.cache.DelayCache; +import org.platon.p2p.common.PeerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author yangzhou + * @create 2018-04-26 19:54 + */ +@Component +public class RequestManager { + + private static Logger logger = LoggerFactory.getLogger(RequestManager.class); + + private static DelayCache.TimeoutCallbackFunction requestTimeoutCallback = new DelayCache.TimeoutCallbackFunction() { + + @Override + public void timeout(Object key, Object value) { + String transactionId = (String)key; + CompletableFuture future = (CompletableFuture)value; + if (future.isDone()) { + logger.trace("message is complete before timeout, txId:={}.", transactionId); + return; + } + future.completeExceptionally(new Exception(String.format("Request %s times out", transactionId))); + logger.warn("Complete pending future exceptionally with timeout, txId:={}.", transactionId); + } + }; + private static DelayCache> responseFutureCache = new DelayCache<>(requestTimeoutCallback); + + + + public void put(String id, CompletableFuture responseFuture) { + + + responseFutureCache.put(id, responseFuture, PeerConfig.getInstance().getMessageResponseTimeout(), TimeUnit.SECONDS); + logger.debug("Save pending future, txId:={}.", id); + } + + public CompletableFuture remove(String messageTxId){ + return responseFutureCache.remove(messageTxId); + } + + + + public void handleResponse(String id, Message msg) { + + + + CompletableFuture pending = this.remove(id); + + logger.trace("All pending Futures :::::::::::::: " + responseFutureCache.getSize()); + if (pending != null) { + logger.debug("Complete pending future, txId:={}.", id); + pending.complete(msg); + } + } + + public void handleException(String id, Throwable throwable) { + logger.trace("Received throwable, txId:={}.", id); + logger.trace("All pending Futures :::::::::::::: " + responseFutureCache.getSize()); + + + CompletableFuture pending = this.remove(id); + if (pending != null) { + logger.debug("Complete pending future exceptionally, txId:={}.", id); + pending.completeExceptionally(throwable); + } + } +} diff --git a/p2p/src/main/java/org/platon/p2p/session/ChannelAttribute.java b/p2p/src/main/java/org/platon/p2p/session/ChannelAttribute.java new file mode 100644 index 0000000..1304d20 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/session/ChannelAttribute.java @@ -0,0 +1,13 @@ +package org.platon.p2p.session; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/8/4, lvxiaoyi, Initial Version. + */ +public class ChannelAttribute { + + +} diff --git a/p2p/src/main/java/org/platon/p2p/session/CreateSessionHandler.java b/p2p/src/main/java/org/platon/p2p/session/CreateSessionHandler.java new file mode 100644 index 0000000..7a90144 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/session/CreateSessionHandler.java @@ -0,0 +1,38 @@ +package org.platon.p2p.session; + + +import io.netty.channel.ChannelHandlerContext; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.proto.session.CreateSession; +import org.platon.p2p.proto.session.SayHello; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author lvxy + * @version 0.0.1 + * @date 2018/8/27 11:17 + */ +@Component("createSessionHandler") +public class CreateSessionHandler { + + private static final Logger logger = LoggerFactory.getLogger(CreateSessionHandler.class); + + @Autowired + private SessionManager sessionManager; + + public void handleCreateSessionRequest(CreateSession createSession, ChannelHandlerContext ctx) { + sessionManager.handleCreateSessionRequest(createSession.getClientNodeId(), createSession.getEndpoint(), createSession.getMessageHash(), createSession.getSignature(), ctx.channel()); + } + + + @PlatonMessageType("SayHello") + public void sayHello(SayHello sayHello, HeaderHelper header){ + sessionManager.sayHello(sayHello.getNodeId(), sayHello.getHello(), sayHello.getFeedback()); + } + + +} diff --git a/p2p/src/main/java/org/platon/p2p/session/Session.java b/p2p/src/main/java/org/platon/p2p/session/Session.java new file mode 100644 index 0000000..a230475 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/session/Session.java @@ -0,0 +1,84 @@ +package org.platon.p2p.session; + +import com.google.protobuf.ByteString; +import io.netty.channel.Channel; +import org.platon.p2p.common.CodecUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.Objects; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/4/28, lvxiaoyi, Initial Version. + */ +public class Session { + + private static final Logger logger = LoggerFactory.getLogger(Session.class); + + + + private ByteString remoteNodeId; + + private Channel connection; + + private Date timestamp; + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + public Channel getConnection() { + return connection; + } + + public void setConnection(Channel connection) { + this.connection = connection; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Session session = (Session) o; + return Objects.equals(remoteNodeId, session.remoteNodeId); + } + + @Override + public int hashCode() { + return Objects.hash(remoteNodeId); + } + + public void refresh(){ + timestamp = new Date(); + } + + public void destroy(){ + connection.close(); + } + + + @Override + public String toString() { + return "Session{" + + "remoteNodeId='" + CodecUtils.toHexString(remoteNodeId) + '\'' + + ", connection=" + connection + + '}'; + } +} diff --git a/p2p/src/main/java/org/platon/p2p/session/SessionManager.java b/p2p/src/main/java/org/platon/p2p/session/SessionManager.java new file mode 100644 index 0000000..b17a398 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/session/SessionManager.java @@ -0,0 +1,357 @@ +package org.platon.p2p.session; + +import com.google.protobuf.ByteString; +import io.netty.channel.Channel; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; +import org.platon.common.cache.DelayCache; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.*; +import org.platon.p2p.attach.LinkController; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.common.PeerConfig; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.plugins.TopologyPlugin; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.session.SayHello; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.security.SignatureException; +import java.util.Date; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * @version 1.0.0 + * @author: lvxiaoyi + *

+ * Revision History: + * 2018/4/28, lvxiaoyi, Initial Version. + */ +@Component("sessionManager") +public class SessionManager { + + private static final Logger logger = LoggerFactory.getLogger(SessionManager.class); + + @Autowired + private LinkController linkController; + + @Autowired + private TopologyPlugin topologyPlugin; + + @Autowired + private RoutingTable routingTable; + + @Autowired + private MessageRouter messageRouter; + + private int createSessionTimeoutInSeconds = PeerConfig.getInstance().getCreateSessionTimeout(); + + private static DelayCache.TimeoutCallbackFunction> createSessionFutureTimeoutCallback = new DelayCache.TimeoutCallbackFunction>() { + + @Override + public void timeout(ByteString remoteNodeId, CompletableFuture sessionFuture) { + logger.error("cannot connect to remote Node Id:={}", CodecUtils.toHexString(remoteNodeId)); + + sessionFuture.completeExceptionally(new Exception(String.format("cannot connect to remote Node Id:=%s", CodecUtils.toHexString(remoteNodeId)))); + } + }; + + + private static DelayCache> createSessionFutureCache = new DelayCache<>(createSessionFutureTimeoutCallback); + + public void addSessionFuture(ByteString remoteNodeId, CompletableFuture sessionFuture){ + logger.debug("===addSessionFuture, remoteNodeId:={}", CodecUtils.toHexString(remoteNodeId)); + + createSessionFutureCache.put(remoteNodeId, sessionFuture, createSessionTimeoutInSeconds, TimeUnit.SECONDS); + } + + public CompletableFuture removeSessionFuture(ByteString remoteNodeId){ + logger.debug("===removeSessionFuture, remoteNodeId:={}", CodecUtils.toHexString(remoteNodeId)); + + return createSessionFutureCache.remove(remoteNodeId); + } + + public void completeSessionFutureExceptionally(ByteString remoteNodeId, Throwable e){ + logger.debug("===completeSessionFutureExceptionally, remoteNodeId:={}", CodecUtils.toHexString(remoteNodeId)); + + CompletableFuture sessionFuture = createSessionFutureCache.remove(remoteNodeId); + sessionFuture.completeExceptionally(e); + } + + + + private static ConcurrentMap sessionMap = new ConcurrentHashMap<>(); + + public Session getSession(ByteString nodeId){ + return sessionMap.get(nodeId); + } + + public LinkController getLinkController() { + return linkController; + } + + public void setLinkController(LinkController linkController) { + this.linkController = linkController; + } + + public TopologyPlugin getTopologyPlugin() { + return topologyPlugin; + } + + public void setTopologyPlugin(TopologyPlugin topologyPlugin) { + this.topologyPlugin = topologyPlugin; + } + + public RoutingTable getRoutingTable() { + return routingTable; + } + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + + public void createSession(ByteString remoteNodeId, ByteString remoteNodePubKey, Channel channel){ + Session session = new Session(); + session.setRemoteNodeId(remoteNodeId); + session.setConnection(channel); + session.setTimestamp(new Date()); + sessionMap.putIfAbsent(remoteNodeId, session); + + + CompletableFuture sessionCompletableFuture = this.removeSessionFuture(remoteNodeId); + if(sessionCompletableFuture!=null){ + sessionCompletableFuture.complete(session); + } + } + + + public void handleCreateSessionRequest(ByteString remoteNodeId, String remoteEndpoint, ByteString messageHash, ByteString signature, Channel channel){ + logger.trace("entry acceptConnect(): {}, {}", remoteNodeId, remoteEndpoint); + + Session session = sessionMap.get(remoteNodeId); + try { + if (session == null) { + + + byte[] pubKey = new byte[0]; + try { + pubKey = WalletUtil.signatureToPubKeyBytes(messageHash.toByteArray(), signature.toByteArray()); + } catch (SignatureException e) { + + + + channel.close(); + throw new Exception("Verify signature error", e); + } + + byte[] tempRemoteNodeId = WalletUtil.computeAddress(pubKey); + + if (!ByteUtils.equals(remoteNodeId.toByteArray(), tempRemoteNodeId)) { + logger.error("remote node id error"); + channel.close(); + throw new Exception("remote node id error"); + + } + + + ((EccDecoder)channel.pipeline().get("eccDecoder")).setRemoteNodeId(remoteNodeId); + ((EccEncoder)channel.pipeline().get("eccEncoder")).setRemoteNodeId(remoteNodeId); + ((EccEncoder)channel.pipeline().get("eccEncoder")).setRemoteNodePubKey(ByteString.copyFrom(pubKey)); + ((NodeServerChannelHandler)channel.pipeline().get("nodeServerChannelHandler")).setRemoteNodeId(remoteNodeId); + + session = new Session(); + session.setConnection(channel); + session.setTimestamp(new Date()); + + + + sessionMap.put(remoteNodeId, session); + SessionNotify.createNotify(remoteNodeId); + + + NodeID remoteNode = NodeID.newBuilder().setId(remoteNodeId).setEndpoint(remoteEndpoint).setPubKey(ByteString.copyFrom(pubKey)).build(); + NodeID temp = routingTable.getNodeID(remoteNodeId); + if (temp == null) { + routingTable.add(remoteNode); + } + } + }catch (Exception e){ + logger.error("Exception:", e); + } + + + CompletableFuture sessionFuture = this.removeSessionFuture(remoteNodeId); + if(sessionFuture!=null){ + logger.debug("回调sessionFuture.complete(session)"); + sessionFuture.complete(session); + } + logger.trace("exit acceptConnect()"); + } + + + + public void refreshSession(String nodeId){ + Session session = sessionMap.get(nodeId); + if(session!=null){ + session.refresh(); + } + } + + + public void closeSession(ByteString remoteNodeId){ + + Session session = sessionMap.remove(remoteNodeId); + if(session!=null){ + + session.destroy(); + session=null; + } + SessionNotify.closeNotify(remoteNodeId); + } + + + + public CompletableFuture getSession(ByteString remoteNodeId, boolean needAttach){ + final CompletableFuture sessionFuture = new CompletableFuture<>(); + + logger.debug("to get session for {}, attach flat:{}", CodecUtils.toHexString(remoteNodeId), needAttach); + Session session = sessionMap.get(remoteNodeId); + try { + if (session != null) { + logger.debug("session exists."); + sessionFuture.complete(session); + }else { + logger.debug("no session exists."); + CompletableFuture sessionFutureExisting = createSessionFutureCache.get(remoteNodeId); + if(sessionFutureExisting!=null){ + logger.debug("session future exists, just return this future."); + return sessionFutureExisting; + } + + logger.debug("try to create session."); + + + NodeID remoteNode = topologyPlugin.getRoutingTable().getNodeID(remoteNodeId); + if (remoteNode != null + && remoteNode.getId().equals(remoteNodeId) + && StringUtils.isNotBlank(remoteNode.getEndpoint()) + && remoteNode.getPubKey()!=null + && !remoteNode.getPubKey().isEmpty()) { + logger.debug("try connecting to remote node id:={}, endpoint:={}", CodecUtils.toHexString(remoteNode.getId()), remoteNode.getEndpoint()); + + + this.addSessionFuture(remoteNodeId, sessionFuture); + + + CompletableFuture connectFuture = connect(remoteNode.getId(), remoteNode.getEndpoint(), remoteNode.getPubKey()); + connectFuture.thenAcceptAsync(success->{ + if(!success){ + if (!needAttach) { + logger.debug("cannot connect to remote node, and needn't try to attach it."); + this.removeSessionFuture(remoteNodeId); + sessionFuture.complete(null); + } else { + CompletableFuture attachFuture = linkController.attach(remoteNodeId); + attachFuture.thenAcceptAsync(new AttachConsumer(false), NodeContext.executor) + .exceptionally(e -> { + logger.error("exception in attaching", e); + this.completeSessionFutureExceptionally(remoteNodeId, e); + sessionFuture.complete(null); + + return null; + }); + } + } + }); + }else{ + logger.warn("there's no detailed info of the remote node."); + if (needAttach){ + this.addSessionFuture(remoteNodeId, sessionFuture); + + CompletableFuture attachFuture = linkController.attach(remoteNodeId); + attachFuture.thenAcceptAsync(new AttachConsumer(true), NodeContext.executor) + .exceptionally(e -> { + logger.error("exception in attaching", e); + this.completeSessionFutureExceptionally(remoteNodeId, e); + return null; + }); + }else{ + sessionFuture.complete(null); + } + } + } + }catch (Exception e){ + sessionFuture.completeExceptionally(e); + } + return sessionFuture; + } + + public void sayHello(ByteString nodeId, String hello, boolean feedback) { + logger.debug("received message:={} from:={}", hello, CodecUtils.toHexString(nodeId)); + if(feedback){ + + + SayHello sayHello = SayHello.newBuilder().setNodeId(NodeContext.localNodeId).setHello("FeedBack:"+hello).build(); + + NodeID remoteNodeId = NodeID.newBuilder().setId(nodeId).build(); + + messageRouter.sendRequest(sayHello, remoteNodeId, MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + } + } + + private class AttachConsumer implements Consumer { + private boolean tryAgain; + + public AttachConsumer(boolean tryAgain) { + this.tryAgain = tryAgain; + } + + @Override + public void accept(NodeID remoteNode) { + if(remoteNode!=null) { + logger.debug("retrieved the remote node info, remoteNode:= {})", remoteNode.toString()); + if(tryAgain) { + connect(remoteNode.getId(), remoteNode.getEndpoint(), remoteNode.getPubKey()); + } + } + } + } + + + + + public Session getSessionRandom(){ + Set> entrySet = sessionMap.entrySet(); + int size = entrySet.size(); + int item = new Random().nextInt(size); + int i = 0; + for(Map.Entry entry : entrySet){ + if (i == item) { + return entry.getValue(); + } + i++; + } + return null; + } + + + + private CompletableFuture connect(ByteString remoteNodeId, String remoteEndpoint, ByteString remoteNodePubKey){ + return CompletableFuture.supplyAsync(()-> { + String[] addresses = remoteEndpoint.split(":"); + return new NodeClient().connect(remoteNodeId, addresses[0], Integer.parseInt(addresses[1]), remoteNodePubKey); + }, NodeContext.executor); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/session/SessionNotify.java b/p2p/src/main/java/org/platon/p2p/session/SessionNotify.java new file mode 100644 index 0000000..da095e6 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/session/SessionNotify.java @@ -0,0 +1,45 @@ +package org.platon.p2p.session; + +import com.google.protobuf.ByteString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * @author yangzhou + * @create 2018-07-23 17:51 + */ +public class SessionNotify { + private static final Logger logger = LoggerFactory.getLogger(SessionNotify.class); + + private final static Set sessionListeners = new CopyOnWriteArraySet<>(); + + + public static void addListener(SessionNotifyCallback listener){ + logger.debug("addSessionFuture session notify serviceName:{}", listener); + sessionListeners.add(listener); + } + + public static void removeListener(SessionNotifyCallback listener){ + sessionListeners.remove(listener); + } + + public static void createNotify(ByteString remoteNodeId) { + for (SessionNotifyCallback callback : sessionListeners) { + callback.create(remoteNodeId); + } + } + + public static void closeNotify(ByteString remoteNodeId) { + for (SessionNotifyCallback callback : sessionListeners) { + callback.close(remoteNodeId); + } + } + + public interface SessionNotifyCallback{ + void create(ByteString remoteNodeId); + void close(ByteString remoteNodeId); + } +} diff --git a/p2p/src/main/java/org/platon/p2p/storage/StorageController.java b/p2p/src/main/java/org/platon/p2p/storage/StorageController.java new file mode 100644 index 0000000..128176c --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/storage/StorageController.java @@ -0,0 +1,214 @@ +package org.platon.p2p.storage; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import org.apache.commons.lang3.tuple.Pair; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.storage.*; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * @author yangzhou + * @create 2018-05-04 16:42 + */ +@Component("storageController") +public class StorageController { + private static Logger logger = LoggerFactory.getLogger(StorageController.class); + + @Autowired + private MessageRouter messageRouter; + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + public CompletableFuture set(final ResourceID key, final byte[] value) { + return set(key, value, -1); + } + + public CompletableFuture set(final ResourceID key, final byte[] value, long lifetime) { + final CompletableFuture res = new CompletableFuture<>(); + SetStoreMessage.Builder setStoreMsgBuilder = SetStoreMessage.newBuilder(); + + setStoreMsgBuilder.setResourceId(key); + + StoredData.Builder storedDataBuilder = StoredData.newBuilder(); + storedDataBuilder.setLifeTime(lifetime); + storedDataBuilder.setStorageTime(System.currentTimeMillis()); + storedDataBuilder.setValue(ByteString.copyFrom(value)); + setStoreMsgBuilder.setStoredData(storedDataBuilder); + + CompletableFuture futMsg = messageRouter.sendRequest( + setStoreMsgBuilder.build(), Collections.singletonList(NodeUtils.toRoutableID(key)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + futMsg.thenAcceptAsync(ansMsg->{ + res.complete((SetStoreRespMessage)ansMsg); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture get(final ResourceID key) { + final CompletableFuture res = new CompletableFuture(); + GetStoreMessage.Builder storeMessageBuilder = GetStoreMessage.newBuilder(); + storeMessageBuilder.setResourceId(key); + + StoredData.Builder storedDataBuilder = StoredData.newBuilder(); + + CompletableFuture fut = messageRouter.sendRequest(storeMessageBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(key)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg->{ + GetStoreRespMessage storeMsg = (GetStoreRespMessage)futMsg; + if (!storeMsg.hasStoredData()) { + res.complete(null); + } else { + res.complete(storeMsg.getStoredData().getValue().toByteArray()); + } + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture del(final ResourceID key) { + final CompletableFuture res = new CompletableFuture<>(); + + DelStoreMessage.Builder delStoreMsgBuilder = DelStoreMessage.newBuilder(); + delStoreMsgBuilder.setResourceId(key); + + + CompletableFuture futMsg = messageRouter.sendRequest(delStoreMsgBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(key)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, + true); + + futMsg.thenAcceptAsync(ansMsg->{ + res.complete((DelStoreRespMessage)ansMsg); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture hset(final ResourceID name, final byte[] key, final byte[] value) { + return hset(name, key, value, -1); + } + + public CompletableFuture hset(final ResourceID name, final byte[] key, final byte[] value, long lifetime) { + final CompletableFuture res = new CompletableFuture<>(); + + HSetStoreMessage.Builder hSetStoreMsgBuilder = HSetStoreMessage.newBuilder(); + hSetStoreMsgBuilder.setResourceId(name); + hSetStoreMsgBuilder.setKey(ByteString.copyFrom(key)); + StoredData.Builder dataBuilder = StoredData.newBuilder(); + + dataBuilder.setValue(ByteString.copyFrom(value)); + dataBuilder.setLifeTime(lifetime); + dataBuilder.setStorageTime(System.currentTimeMillis()); + hSetStoreMsgBuilder.setStoredData(dataBuilder); + CompletableFuture futMsg = messageRouter.sendRequest( hSetStoreMsgBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(name)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + futMsg.thenAcceptAsync(ansMsg->{ + res.complete((HSetStoreRespMessage)ansMsg); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture hget(final ResourceID name, final byte[] key) { + final CompletableFuture res = new CompletableFuture<>(); + HGetStoreMessage.Builder hGetStoreMsgBuilder = HGetStoreMessage.newBuilder(); + hGetStoreMsgBuilder.setResourceId(name); + hGetStoreMsgBuilder.setKey(ByteString.copyFrom(key)); + StoredData.Builder dataBuilder = StoredData.newBuilder(); + dataBuilder.setStorageTime(System.currentTimeMillis()); + CompletableFuture fut = messageRouter.sendRequest( hGetStoreMsgBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(name)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, true); + + fut.thenAcceptAsync(futMsg -> { + HGetStoreRespMessage hGetStoreRespMsg = (HGetStoreRespMessage)futMsg; + if (hGetStoreRespMsg.getStoredDataList().isEmpty()){ + res.complete(null); + } + res.complete(hGetStoreRespMsg.getStoredDataList().get(0).getValue().toByteArray()); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture>> hgetAll(final ResourceID name) { + final CompletableFuture>> res = new CompletableFuture<>(); + + HGetAllStoreMessage.Builder hGetAllStoreMsgBuilder = HGetAllStoreMessage.newBuilder(); + hGetAllStoreMsgBuilder.setResourceId(name); + StoredData.Builder data = StoredData.newBuilder(); + data.setStorageTime(System.currentTimeMillis()); + CompletableFuture fut = messageRouter.sendRequest( + hGetAllStoreMsgBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(name)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, + true); + + fut.thenAcceptAsync(futMsg -> { + HGetAllStoreRespMessage hGetAllStoreRespMsg = (HGetAllStoreRespMessage)futMsg; + List> kvList = new ArrayList<>(); + for (StoredData v : hGetAllStoreRespMsg.getStoredDataList()){ + kvList.add(Pair.of(v.getKey().toByteArray(), v.getValue().toByteArray())); + } + res.complete(kvList); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + + public CompletableFuture hdel(final ResourceID name, final byte[] key) { + final CompletableFuture res = new CompletableFuture<>(); + + HDelStoreMessage.Builder hDelStoreMsgBuilder = HDelStoreMessage.newBuilder(); + hDelStoreMsgBuilder.setResourceId(name); + hDelStoreMsgBuilder.setKey(ByteString.copyFrom(key)); + StoredData.Builder data = StoredData.newBuilder(); + + data.setStorageTime(System.currentTimeMillis()); + CompletableFuture futMsg = messageRouter.sendRequest(hDelStoreMsgBuilder.build(), + Collections.singletonList(NodeUtils.toRoutableID(name)), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, + true); + + futMsg.thenAcceptAsync(ansMsg->{ + res.complete((HDelStoreRespMessage)ansMsg); + }, NodeContext.executor).exceptionally(throwable -> { + res.completeExceptionally(throwable); + return null; + }); + return res; + } + +} diff --git a/p2p/src/main/java/org/platon/p2p/storage/StorageService.java b/p2p/src/main/java/org/platon/p2p/storage/StorageService.java new file mode 100644 index 0000000..a0a3317 --- /dev/null +++ b/p2p/src/main/java/org/platon/p2p/storage/StorageService.java @@ -0,0 +1,436 @@ +package org.platon.p2p.storage; + +import com.google.protobuf.ByteString; +import org.apache.commons.lang3.tuple.Pair; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.handler.PlatonMessageType; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.storage.*; +import org.platon.p2p.router.MessageRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** + * @author yangzhou + * @create 2018-04-26 14:46 + */ +@Component("storageService") +public class StorageService { + + private static Logger logger = LoggerFactory.getLogger(StorageService.class); + + @Autowired + private DB db; + + @Autowired + private MessageRouter messageRouter; + + @Autowired + private RoutingTable routingTable; + + public void setDb(DB db) { + this.db = db; + } + + public void setMessageRouter(MessageRouter messageRouter) { + this.messageRouter = messageRouter; + } + + public void setRoutingTable(RoutingTable routingTable) { + this.routingTable = routingTable; + } + + @PlatonMessageType("SetStoreMessage") + public void set(SetStoreMessage setStoreMessage, HeaderHelper header){ + logger.trace(setStoreMessage.toString()); + + + boolean needReplica = false; + + List replicaNode = new ArrayList<>(); + List destList = new ArrayList<>(); + + if (setStoreMessage.getReplica() == 0) { + List nodeIDList = routingTable.getNextHops( + RoutableID.newBuilder().setId(setStoreMessage.getResourceId().getId()) + .setType(RoutableID.DestinationType.NODEIDTYPE).build(), 4); + + destList.addAll(NodeUtils.nodeIdListToRoutableIdList(nodeIDList)); + needReplica = true; + } + + if(!replicaNode.isEmpty()) { + replicaNode.add(NodeUtils.toRoutableID(routingTable.getLocalNode())); + setStoreMessage.toBuilder().addAllReplicaNodeId(replicaNode); + } + + ByteString out = ProtoBufHelper.encodeIceData(toStoreDataEntry(setStoreMessage)); + + SetStoreRespMessage.Builder statusMessageBuilder = SetStoreRespMessage.newBuilder().setStatus(StoreStatus.SUCCESS); + + try { + + + List lastReplicaNodes = new ArrayList<>(); + StoreDataEntry lastStoreEntry = getReplicaStoreMessage(setStoreMessage.getResourceId().toByteArray()); + if (lastStoreEntry != null) { + lastReplicaNodes = lastStoreEntry.getReplicaNodeIdList(); + } + + + db.set(setStoreMessage.getResourceId().toByteArray(), out.toByteArray()); + + + if (needReplica && !destList.isEmpty()) { + setStoreMessage.toBuilder().setReplica(1); + messageRouter.sendRequest( + setStoreMessage, destList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, false); + } + + + lastReplicaNodes.removeAll(replicaNode); + if (!lastReplicaNodes.isEmpty() && lastStoreEntry.getReplica() == 0) { + List removeDestList = new ArrayList<>(); + removeDestList.addAll(lastReplicaNodes); + DelStoreMessage.Builder delStoreMessage = DelStoreMessage.newBuilder(); + delStoreMessage.setResourceId(setStoreMessage.getResourceId()); + messageRouter.sendRequest(delStoreMessage.build(), removeDestList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, false); + } + + } catch (Exception e) { + statusMessageBuilder.setStatus(StoreStatus.FAILED); + statusMessageBuilder.setError(e.getMessage()); + logger.error("error:", e); + } + messageRouter.sendResponse( + statusMessageBuilder.build(), header.txId(), header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + @PlatonMessageType("SetStoreRespMessage") + public void setResp(SetStoreRespMessage msg, HeaderHelper header) { + messageRouter.handleResponse(header.txId(), msg); + } + + @PlatonMessageType("GetStoreMessage") + public void get(GetStoreMessage msg, HeaderHelper header) { + + GetStoreRespMessage.Builder getStoreRespMsgBuilder = GetStoreRespMessage.newBuilder(); + try { + + byte[] value = db.get(msg.getResourceId().getId().toByteArray()); + + + StoreDataEntry storeDataEntry = ProtoBufHelper.decodeIceData(value, StoreDataEntry.parser()); + + StoredData data = storeDataEntry.getStoredData(); + + + if (data != null) { + long now = System.currentTimeMillis(); + if (data.getLifeTime() != -1 && data.getStorageTime() + data.getLifeTime() < now) { + db.hdel(msg.getResourceId().getId().toByteArray(), data.getKey().toByteArray()); + } else { + getStoreRespMsgBuilder.setStoredData(data); + } + } + logger.trace("get data:" + getStoreRespMsgBuilder.build()); + + } catch (Exception e) { + logger.error("error:", e); + } finally { + messageRouter.sendResponse( + getStoreRespMsgBuilder.build(),header.txId(), header.viaToDest(), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + } + + @PlatonMessageType("GetStoreRespMessage") + public void getResp(GetStoreRespMessage msg, HeaderHelper header){ + System.out.println("getResp:" + msg.toString()); + messageRouter.handleResponse(header.txId(), msg); + } + + @PlatonMessageType("DelStoreMessage") + public void del(DelStoreMessage msg, HeaderHelper header){ + DelStoreRespMessage.Builder delStoreRespMsgBuilder = DelStoreRespMessage.newBuilder().setStatus(StoreStatus.SUCCESS); + + try { + + StoreDataEntry lastStoreEntry = getReplicaStoreMessage(msg.getResourceId().getId().toByteArray()); + + db.del(msg.getResourceId().getId().toByteArray()); + + if (lastStoreEntry != null + && !lastStoreEntry.getReplicaNodeIdList().isEmpty() + && lastStoreEntry.getReplica() == 0) { + + List removeDestList = new ArrayList<>(); + removeDestList.addAll(lastStoreEntry.getReplicaNodeIdList()); + DelStoreMessage.Builder removeMsgBuilder = DelStoreMessage.newBuilder(); + removeMsgBuilder.setResourceId(msg.getResourceId()); + messageRouter.sendRequest( + removeMsgBuilder.build(), removeDestList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, false); + } + } catch (Exception e) { + delStoreRespMsgBuilder.setStatus(StoreStatus.FAILED); + delStoreRespMsgBuilder.setError(e.getMessage()); + logger.error("error:", e); + } + + messageRouter.sendResponse( + delStoreRespMsgBuilder.build(), + header.txId(), + header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + private StoreDataEntry getReplicaStoreMessage(byte[] id) { + StoreDataEntry storeDataEntry = null; + try { + byte[] lastValue = db.get(id); + if (lastValue != null) { + + storeDataEntry = ProtoBufHelper.decodeIceData(lastValue, StoreDataEntry.parser()); + + } else { + return null; + } + } catch (Exception e) { + return null; + } + return storeDataEntry; + } + + private HashStoreDataEntry getReplicaHashStoreMessage(byte[] name, byte[] key) { + + + HashStoreDataEntry hashStoreDataEntry = null; + try { + byte[] lastValue = db.hget(name, key); + + if (lastValue != null) { + hashStoreDataEntry = ProtoBufHelper.decodeIceData(lastValue, HashStoreDataEntry.parser()); + } else { + return null; + } + } catch (Exception e) { + return null; + } + return hashStoreDataEntry; + } + + @PlatonMessageType("DelStoreRespMessage") + public void delResp(DelStoreRespMessage msg, HeaderHelper header) { + messageRouter.handleResponse(header.txId(), msg); + } + + @PlatonMessageType("HSetStoreMessage") + public void hset(HSetStoreMessage msg, HeaderHelper header){ + + boolean needReplica = false; + + List replicaNode = new ArrayList<>(); + List destList =new ArrayList<>(); + if (msg.getReplica() == 0) { + replicaNode = routingTable.getNextHops(msg.getResourceId(), 4); + replicaNode.remove(routingTable.getLocalNode()); + destList.addAll(NodeUtils.nodeIdListToRoutableIdList(replicaNode)); + needReplica = true; + } + + if(!replicaNode.isEmpty()){ + replicaNode.add(routingTable.getLocalNode()); + msg.toBuilder().addAllReplicaNodeId(NodeUtils.nodeIdListToRoutableIdList(replicaNode)); + } + + + byte[] out = ProtoBufHelper.encodeIceData(toHashStoreDataEntry(msg)).toByteArray(); + + HSetStoreRespMessage.Builder hSetStoreRespMsgBuilder = HSetStoreRespMessage.newBuilder().setStatus(StoreStatus.SUCCESS); + try { + + + List lastReplicaNodes = new ArrayList<>(); + HashStoreDataEntry lastStoreMsg = getReplicaHashStoreMessage(msg.getResourceId().getId().toByteArray(), msg.getKey().toByteArray()); + if (lastStoreMsg != null) { + lastReplicaNodes = lastStoreMsg.getReplicaNodeIdList(); + } + + db.hset(msg.getResourceId().getId().toByteArray(), msg.getKey().toByteArray(), out); + + + if (needReplica && !destList.isEmpty()) { + msg.toBuilder().setReplica(1); + messageRouter.sendRequest( + msg, destList, MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, false); + } + + + lastReplicaNodes.removeAll(replicaNode); + if (!lastReplicaNodes.isEmpty() && lastStoreMsg.getReplica() == 0) { + List removeDestList = new ArrayList<>(); + removeDestList.addAll(lastReplicaNodes); + HDelStoreMessage.Builder removeMsgBuilder = HDelStoreMessage.newBuilder(); + removeMsgBuilder.setResourceId(msg.getResourceId()); + messageRouter.sendRequest( + removeMsgBuilder.build(), + removeDestList, + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, + false); + } + + } catch (Exception e) { + hSetStoreRespMsgBuilder.setStatus(StoreStatus.FAILED); + hSetStoreRespMsgBuilder.setError(e.getMessage()); + logger.error("error:", e); + } + + messageRouter.sendResponse( + hSetStoreRespMsgBuilder.build(), header.txId(), header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + @PlatonMessageType("HSetStoreRespMessage") + public void hsetResp(HSetStoreRespMessage msg, HeaderHelper header) { + + messageRouter.handleResponse(header.txId(), msg); + } + + @PlatonMessageType("HGetStoreMessage") + public void hget(HGetStoreMessage msg, HeaderHelper header){ + HGetStoreRespMessage.Builder hGetStoreMsgBuilder = HGetStoreRespMessage.newBuilder(); + + + try { + + byte[] value = db.hget(msg.getResourceId().getId().toByteArray(), msg.getKey().toByteArray()); + StoredData data = ProtoBufHelper.decodeIceData(value, HashStoreDataEntry.parser()).getStoredData(); + + if (data != null) { + long now = System.currentTimeMillis(); + if (data.getLifeTime() != -1 && data.getStorageTime() + data.getLifeTime() < now) { + db.hdel(msg.getResourceId().getId().toByteArray(), data.getKey().toByteArray()); + } else { + data.toBuilder().setKey(msg.getKey()); + hGetStoreMsgBuilder.addStoredData(data); + } + } + + } catch (Exception e) { + logger.error("error:", e); + } finally { + messageRouter.sendResponse(hGetStoreMsgBuilder.build(), header.txId(), header.viaToDest(), MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + + } + } + + @PlatonMessageType("HGetAllStoreMessage") + public void hgetAll(HGetAllStoreMessage msg, HeaderHelper header){ + HGetAllStoreRespMessage.Builder hGetAllStoreRespMsgBuilder = HGetAllStoreRespMessage.newBuilder(); + + try { + + List> res = db.hgetAll(msg.getResourceId().getId().toByteArray()); + List storeList = new ArrayList<>(); + for (Map.Entry m : res) { + StoredData data = ProtoBufHelper.decodeIceData(m.getValue(), HashStoreDataEntry.parser()).getStoredData(); + if (data != null) { + long now = System.currentTimeMillis(); + if (data.getLifeTime() != -1 && data.getStorageTime() + data.getLifeTime() < now) { + db.hdel(msg.getResourceId().getId().toByteArray(), data.getKey().toByteArray()); + } else { + data.toBuilder().setKey(ByteString.copyFrom(m.getKey())).build(); + storeList.add(data); + } + } + } + hGetAllStoreRespMsgBuilder.addAllStoredData(storeList); + } catch (Exception e) { + logger.error("error:", e); + } finally { + messageRouter.sendResponse( + hGetAllStoreRespMsgBuilder.build(), + header.txId(), + header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + + } + + } + + @PlatonMessageType("HGetStoreRespMessage") + public void hgetResp(HGetStoreRespMessage msg, HeaderHelper header){ + messageRouter.handleResponse(header.txId(), msg); + } + + @PlatonMessageType("HDelStoreMessage") + public void hdel(HDelStoreMessage msg, HeaderHelper header){ + HDelStoreRespMessage.Builder hDelStoreRespMsgBuilder = HDelStoreRespMessage.newBuilder().setStatus(StoreStatus.SUCCESS); + + try { + + HashStoreDataEntry lastStoreMessage = getReplicaHashStoreMessage(msg.getResourceId().getId().toByteArray(), msg.getKey().toByteArray()); + db.hdel(msg.getResourceId().getId().toByteArray(), msg.getKey().toByteArray()); + + if (lastStoreMessage != null && !lastStoreMessage.getReplicaNodeIdList().isEmpty() && lastStoreMessage.getReplica() == 0) { + + HDelStoreMessage removeMsg = HDelStoreMessage.newBuilder().setResourceId(msg.getResourceId()).setKey(msg.getKey()).build(); + + messageRouter.sendRequest( + removeMsg, + lastStoreMessage.getReplicaNodeIdList(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION, + false); + } + } catch (Exception e) { + hDelStoreRespMsgBuilder.setStatus(StoreStatus.FAILED); + hDelStoreRespMsgBuilder.setError(e.getMessage()); + logger.error("error:", e); + } + + messageRouter.sendResponse( + hDelStoreRespMsgBuilder.build(), header.txId(), header.viaToDest(), + MessageRouter.ForwardingOptionType.FORWARD_CONNECTION); + } + + @PlatonMessageType("HDelStoreRespMessage") + public void hdelResp(HDelStoreRespMessage msg, HeaderHelper header) { + messageRouter.handleResponse(header.txId(), msg); + } + + + private StoreDataEntry toStoreDataEntry(SetStoreMessage setStoreMessage) { + return StoreDataEntry.newBuilder() + .setReplica(setStoreMessage.getReplica()) + .addAllReplicaNodeId(setStoreMessage.getReplicaNodeIdList()) + .setResourceId(setStoreMessage.getResourceId()) + .setStoredData(setStoreMessage.getStoredData()) + .build(); + } + + private HashStoreDataEntry toHashStoreDataEntry(HSetStoreMessage hSetStoreMessage) { + return HashStoreDataEntry.newBuilder() + .setReplica(hSetStoreMessage.getReplica()) + .addAllReplicaNodeId(hSetStoreMessage.getReplicaNodeIdList()) + .setResourceId(hSetStoreMessage.getResourceId()) + .setStoredData(hSetStoreMessage.getStoredData()) + .setKey(hSetStoreMessage.getKey()) + .build(); + } + + +} + diff --git a/p2p/src/main/resources/config/node.conf b/p2p/src/main/resources/config/node.conf new file mode 100644 index 0000000..2df31f1 --- /dev/null +++ b/p2p/src/main/resources/config/node.conf @@ -0,0 +1,99 @@ +node = { + ip=192.168.7.109 +} + +peer = { + listen.port=12345 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + private-key=a392604efc2fad9c0b3da43b5f698a2e3f270f170d859912be0d54742275c5f6 + #in seconds + create.session.timeout = 10 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 12 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 30 + + active.list = + [ + { + ip=192.168.7.113 + port=11001 + public-key=0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76 + } + ] +} + +grpc = { + listen.port = 11001 +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} + + +platon = { + keystore.dir = keystore + + genesis = { + resourcePath = genesis.json + + + # filePath = /some/path/frontier.json + } + + database = { + dir = database + + source = rocksdb + + + incompatibleDatabaseBehavior = EXIT + } + + mine = { + start = true + + mineBlockTime = 1000 + + coinbase = "0000000000000000000000000000000000000000" + } + + validator = { + timeliness.ancestorBlockCache.size = 50 + timeliness.pendingBlockCache.size = 50 + timeliness.pendingTxCache.size = 5000 + } +} + + + diff --git a/p2p/src/main/resources/config/node.properties b/p2p/src/main/resources/config/node.properties new file mode 100644 index 0000000..df52515 --- /dev/null +++ b/p2p/src/main/resources/config/node.properties @@ -0,0 +1,121 @@ + +NodeServer.NodeId=3056301006072a8648cf + + +NodeServer.overlayName=yyyyyy + + +NodeServer.AbandonDuplicatedRequestMessageDuration=30 + + +#Reachable.Remote.Endpoints=relayNode:tcp -h localhost -p 10003 + + +#Reachable.Relay.Endpoints=6e6f646531:tcp -h localhost -p 10001 + +NodeServer.service=Session:com.platon.p2p.session.SessionServiceImpl:1,StorageService:com.platon.p2p.storage.StorageServiceImpl:1,Routing:com.platon.p2p.plugins.PluginImpl:1,LinkService:com.platon.p2p.attach.LinkServiceImpl:1,ServiceDiscovery:com.platon.p2p.redir.ServiceDiscoveryImpl:1,ChatService:com.platon.p2p.chat.ChatServiceImpl:1,Voice:com.platon.p2p.test.VoiceServiceImpl:1 +NodeServer.admin.service=admin:com.platon.p2p.admin.ContextImpl:1,chat:com.platon.p2p.chat.ChatServiceAdminImpl:1,clientAccess:com.platon.p2p.admin.ClientAccessImpl:1,systemState:com.platon.p2p.admin.SystemStateImpl:1,context:com.platon.p2p.admin.ContextImpl:1 + + +#NodeServer.relay=*:999999 + + + +NodeServer.HttpServer=1 + + +NodeServer.HttpServer.context=config/context.xml + + + +NodeServer.ChatServer=1 + + + +Ice.Default.Package=com.platon.p2p.slice + + +# +# Configure endpoints for local node server. +# +NodeServer.Endpoints=tcp -h localhost -p 10001 +NodeServerAdmin.Endpoints=ws -h localhost -p 11001 + + + +Ice.Default.Timeout=60000 + + +# +# CloseOff 0 Disables automatic connection closure. A connection will be closed when the communicator is destroyed, when there's a network failure or when the peer closes it. +# CloseOnIdle 1 Gracefully closes a connection that has been idle for the configured timeout period. A connection is idle if it has no pending outgoing or incoming requests. +# CloseOnInvocation 2 Forcefully closes a connection that has been idle for the configured timeout period, but only if the connection has pending outgoing requests. This is useful when you don't want a connection to be closed when idle but still want the connection to be forcefully closed when invocations are in progress and no messages are received from the server (potentially indicating that the server is dead). This should be used with a heartbeat configuration where heartbeats are sent at regular intervals by a server while an invocation is being dispatched. +# CloseOnInvocationAndIdle 3 Combines the behaviors of CloseOnIdle and CloseOnInvocation. +# CloseOnIdleForceful 4 Forcefully closes a connection that has been idle for the configured timeout period, regardless of whether the connection has pending outgoing or incoming requests. This is typically used together with a heartbeat configuration that keeps idle connections alive. +# +Ice.ACM.Close=0 + +# +# HeartbeatOff 0 Disables client-side heartbeats. +# HeartbeatOnInvocation 1 Send a heartbeat at regular intervals if there are pending incoming requests. +# HeartbeatOnIdle 2 Send a heartbeat at regular intervals when the connection is idle. +# HeartbeatAlways 3 +# +Ice.ACM.Heartbeat=3 + + +Ice.ACM.Timeout=30 + + + + +Ice.ThreadPool.Client.Size=100 +Ice.ThreadPool.Client.SizeWarn=50 + + +Ice.ThreadPool.Server.Size=100 +Ice.ThreadPool.Server.SizeWarn=50 + + +Ice.Trace.ThreadPool=1 + + +# +# Warn about connection exceptions +# +Ice.Warn.Connections=1 + +# +# Network Tracing +# +# 0 = no network tracing +# 1 = trace connection establishment and closure +# 2 = like 1, but more detailed +# 3 = like 2, but also trace data transfer +# +#Ice.Trace.Network=1 + +# +# Protocol Tracing +# +# 0 = no protocol tracing +# 1 = trace protocol messages +# +#Ice.Trace.Protocol=1 + +# +# Security Tracing +# +# 0 = no security tracing +# 1 = trace messages +# +#IceSSL.Trace.Security=1 + +# +# SSL Configuration +# +#Ice.Plugin.IceSSL=com.zeroc.IceSSL.PluginFactory +#IceSSL.VerifyPeer=0 +#IceSSL.DefaultDir=../../../certs +#IceSSL.Keystore=nodeServer.jks +#IceSSL.Password=password \ No newline at end of file diff --git a/p2p/src/main/resources/logback.xml b/p2p/src/main/resources/logback.xml new file mode 100644 index 0000000..f5cb5c2 --- /dev/null +++ b/p2p/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %thread %logger{36} - %msg %n + + + TRACE + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/p2p/src/test/java/org/platon/p2p/DelayCacheTest.java b/p2p/src/test/java/org/platon/p2p/DelayCacheTest.java new file mode 100644 index 0000000..607194e --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/DelayCacheTest.java @@ -0,0 +1,28 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import org.platon.common.cache.DelayCache; +import org.platon.common.utils.Numeric; +import org.platon.p2p.common.CodecUtils; + +import java.util.concurrent.TimeUnit; + +public class DelayCacheTest { + public static void main(String[] args) throws InterruptedException { + DelayCache testCache = new DelayCache<>(); + testCache.setTimeoutCallback((key, value) ->{ + System.out.println("key:" + CodecUtils.toHexString((ByteString)key)); + System.out.println("value:" + value); + }); + + byte[] bytes = Numeric.hexStringToByteArray("0x9f6d816d91405c36d3c408f2bcdae97f5e5df182"); + + testCache.put(ByteString.copyFrom(bytes), 1, 3, TimeUnit.SECONDS); + + testCache.remove(ByteString.copyFrom(bytes)); + + TimeUnit.SECONDS.sleep(100); + + + } +} diff --git a/p2p/src/test/java/org/platon/p2p/ECKeyTools.java b/p2p/src/test/java/org/platon/p2p/ECKeyTools.java new file mode 100644 index 0000000..9129c9a --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/ECKeyTools.java @@ -0,0 +1,24 @@ +package org.platon.p2p; + +import org.platon.common.utils.Numeric; +import org.platon.crypto.ECKey; +import org.platon.crypto.WalletUtil; + +import java.nio.charset.StandardCharsets; + +public class ECKeyTools { + public static void main(String[] args){ + ECKey ecKey = new ECKey(); + + byte[] priKey = ecKey.getPrivKeyBytes(); + byte[] pubKey = ecKey.getPubKey(); + + System.out.println(Numeric.toHexString(ecKey.getPrivKeyBytes())); + System.out.println(Numeric.toHexString(ecKey.getPubKey())); + + byte[] buf = "this is a test message.".getBytes(StandardCharsets.UTF_8); + + byte[] encryptedBytes = WalletUtil.encrypt(buf, NodeContext.ecKey); + + } +} diff --git a/p2p/src/test/java/org/platon/p2p/NodeClientTest.java b/p2p/src/test/java/org/platon/p2p/NodeClientTest.java new file mode 100644 index 0000000..bd96a2c --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/NodeClientTest.java @@ -0,0 +1,49 @@ +package org.platon.p2p; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultMessageSizeEstimator; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; + +public class NodeClientTest { + + public void connect(String host, int port) { + try { + ChannelFuture channelFuture = connectAsync(host, port); + + channelFuture.sync(); + + channelFuture.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ChannelFuture connectAsync(String host, int port) { + Bootstrap b = new Bootstrap(); + b.group(new NioEventLoopGroup()); + b.channel(NioSocketChannel.class); + + b.option(ChannelOption.SO_KEEPALIVE, true); + b.option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT); + b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000); + b.remoteAddress(host, port); + + + return b.connect(); + } + + public static void main(String[] args) throws Exception { + NodeClientTest timerClient = new NodeClientTest(); + timerClient.test(); + + } + + private void test(){ + connect("192.168.7.113", 11001); + } + + +} diff --git a/p2p/src/test/java/org/platon/p2p/TestNodeServer1.java b/p2p/src/test/java/org/platon/p2p/TestNodeServer1.java new file mode 100644 index 0000000..89e220f --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/TestNodeServer1.java @@ -0,0 +1,87 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import org.apache.commons.lang3.StringUtils; +import org.platon.common.config.ConfigProperties; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.session.SayHello; +import org.platon.p2p.router.MessageRouter; + +public class TestNodeServer1 { + public static void main(String[] args){ + + /*byte[] byteString = Numeric.hexStringToByteArray("0x506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba645c0b7b58158babbfa6c6cd5a48aa7340a8749176b120e8516216787a13dc76"); + + ByteString bb = ByteString.copyFrom(byteString); + System.out.println(CodecUtils.toHexString(bb)); + + byte[] addressByte = WalletUtil.computeAddress(byteString); + + ByteString b1 = ByteString.copyFrom(addressByte); + + byte[] addressByte2 = WalletUtil.computeAddress(byteString); + ByteString b2 = ByteString.copyFrom(addressByte2); + + System.out.println(b1.equals(b2));*/ + + ConfigProperties.setConfigPath(""); + NodeServer server1 = new NodeServer(); + server1.startup(); + + new TestNodeServer1().cmdline(); + } + + private void cmdline(){ + java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); + String line = null; + + do { + try { + System.out.print("==> "); + System.out.flush(); + line = in.readLine(); + if (line == null) { + break; + } + + if (line.startsWith("sayHello")) { + String[] params = line.split(" "); + sayHello(StringUtils.trim(params[1]), StringUtils.trim(params[2])); + } else if (line.equals("x")) { + + } else { + System.out.println("unknown command `" + line + "'"); + menu(); + } + } catch ( Exception ex) { + ex.printStackTrace(); + } + } + while (!line.equals("x")); + } + + + private void sayHello(String nodeId, String hello) { + + byte[] remoteNodeIdBytes = Numeric.hexStringToByteArray(nodeId); + ByteString remoteNodeId = ByteString.copyFrom(remoteNodeIdBytes); + NodeID nodeID = NodeID.newBuilder().setId(remoteNodeId).build(); + + SayHello sayHello = SayHello.newBuilder().setNodeId(NodeContext.localNodeId).setHello(hello).setFeedback(true).build(); + + MessageRouter messageRouter = SpringContextUtil.getBean("messageRouter"); + + + messageRouter.sendRequest(sayHello, nodeID, MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + } + + private static void menu() { + System.out.println( + "usage:\n" + + "sayHello remoteNodeId something: send something to remoteNodeId\n" + + "x: exit\n" + + "?: help\n"); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/TestNodeServer2.java b/p2p/src/test/java/org/platon/p2p/TestNodeServer2.java new file mode 100644 index 0000000..86478e4 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/TestNodeServer2.java @@ -0,0 +1,73 @@ +package org.platon.p2p; + +import com.google.protobuf.ByteString; +import org.apache.commons.lang3.StringUtils; +import org.platon.common.config.ConfigProperties; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.session.SayHello; +import org.platon.p2p.router.MessageRouter; + +public class TestNodeServer2 { + public static void main(String[] args){ + ConfigProperties.setConfigPath(""); + NodeServer server1 = new NodeServer(); + server1.startup(); + + new TestNodeServer2().cmdline(); + } + + private void cmdline(){ + java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); + String line = null; + + do { + try { + System.out.print("==> "); + System.out.flush(); + line = in.readLine(); + if (line == null) { + break; + } + + if (line.startsWith("sayHello")) { + String[] params = line.split(" "); + sayHello(StringUtils.trim(params[1]), StringUtils.trim(params[2])); + } else if (line.equals("x")) { + // Nothing to do + } else { + System.out.println("unknown command `" + line + "'"); + menu(); + } + } catch ( Exception ex) { + ex.printStackTrace(); + } + } + while (!line.equals("x")); + } + + + private void sayHello(String nodeId, String hello) { + + byte[] remoteNodeIdBytes = Numeric.hexStringToByteArray(nodeId); + ByteString remoteNodeId = ByteString.copyFrom(remoteNodeIdBytes); + NodeID nodeID = NodeID.newBuilder().setId(remoteNodeId).build(); + + SayHello sayHello = SayHello.newBuilder().setNodeId(NodeContext.localNodeId).setHello(hello).setFeedback(true).build(); + + MessageRouter messageRouter = SpringContextUtil.getBean("messageRouter"); + + + messageRouter.sendRequest(sayHello, nodeID, MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + } + + + private static void menu() { + System.out.println( + "usage:\n" + + "sayHello remoteNodeId something: send something to remoteNodeId\n" + + "x: exit\n" + + "?: help\n"); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/TestSeedServer.java b/p2p/src/test/java/org/platon/p2p/TestSeedServer.java new file mode 100644 index 0000000..716b147 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/TestSeedServer.java @@ -0,0 +1,11 @@ +package org.platon.p2p; + +import org.platon.common.config.ConfigProperties; + +public class TestSeedServer { + public static void main(String[] args){ + ConfigProperties.setConfigPath(""); + NodeServer server1 = new NodeServer(); + server1.startup(); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/TimerClient.java b/p2p/src/test/java/org/platon/p2p/netty/TimerClient.java new file mode 100644 index 0000000..124fcac --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/TimerClient.java @@ -0,0 +1,100 @@ +package org.platon.p2p.netty; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class TimerClient { + private static ExecutorService executor = Executors.newCachedThreadPool(); + + + public static byte[] req = ("QUERY TIME ORDER"+System.getProperty("line.separator")).getBytes(); + + + private static EventLoopGroup workerGroup = new NioEventLoopGroup(0, new ThreadFactory() { + AtomicInteger cnt = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "ClientWorker-" + cnt.getAndIncrement()); + } + }); + + private static Map channelMap = new HashMap<>(); + + public void connect(String host, int port) { + try { + ChannelFuture channelFuture = connectAsync(host, port); + + channelFuture.sync(); + + channelMap.put("" + port, channelFuture.channel()); + + //channelFuture.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ChannelFuture connectAsync(String host, int port) { + Bootstrap b = new Bootstrap(); + b.group(workerGroup); + b.channel(NioSocketChannel.class); + + b.option(ChannelOption.SO_KEEPALIVE, true); + b.option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT); + b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000); + b.remoteAddress(host, port); + + b.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + System.out.println("init channel for remote: " + ch.remoteAddress()); + ch.pipeline().addLast(new LineBasedFrameDecoder(1024)) + .addLast(new StringDecoder()) + .addLast(new TimerClientHandler()); + } + }); + return b.connect(); + } + + public static void main(String[] args) throws Exception { + TimerClient timerClient = new TimerClient(); + timerClient.test(); + + TimeUnit.SECONDS.sleep(5); + + channelMap.entrySet().forEach(entry->{ + ByteBuf message = Unpooled.buffer(TimerClient.req.length); + message.writeBytes(TimerClient.req); + entry.getValue().writeAndFlush(message); + }); + + TimeUnit.SECONDS.sleep(5); + + } + + + private void test(){ + executor.execute(()->{ + this.connect("127.0.0.1", 65534); + }); + executor.execute(()->{ + this.connect("127.0.0.1", 65535); + }); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/TimerClientHandler.java b/p2p/src/test/java/org/platon/p2p/netty/TimerClientHandler.java new file mode 100644 index 0000000..e54890e --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/TimerClientHandler.java @@ -0,0 +1,37 @@ +package org.platon.p2p.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +import java.util.Date; + +public class TimerClientHandler extends SimpleChannelInboundHandler { + + private int counter; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { + + System.out.println("Now is : " + msg + "; the counter is:" + ++counter + " timestamp:" + (new Date())); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ByteBuf message = Unpooled.buffer(TimerClient.req.length); + message.writeBytes(TimerClient.req); + ctx.writeAndFlush(message); + } + + /*public void channelActive(ChannelHandlerContext ctx) throws Exception { + ByteBuf message = null + for(int i=0; i<10; i++){ + message = Unpooled.buffer(req.length); + message.writeBytes(req); + ctx.writeAndFlush(message); + + TimeUnit.SECONDS.sleep(1); + } + }*/ +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/TimerServer.java b/p2p/src/test/java/org/platon/p2p/netty/TimerServer.java new file mode 100644 index 0000000..c11eaf4 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/TimerServer.java @@ -0,0 +1,50 @@ +package org.platon.p2p.netty; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; + + +public class TimerServer { + + private final int port; + + public TimerServer(int port) { + this.port = port; + } + + public void start() throws Exception { + EventLoopGroup group = new NioEventLoopGroup(); + try { + ServerBootstrap sb = new ServerBootstrap(); + sb.group(group) + .channel(NioServerSocketChannel.class) + .localAddress(this.port) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + System.out.println("init channel for remote: " + ch.remoteAddress()); + ch.pipeline().addLast(new LineBasedFrameDecoder(1024)) + .addLast(new StringDecoder()) + .addLast(new TimerServerHandler()); + } + }); + ChannelFuture cf = sb.bind().sync(); + + cf.channel().closeFuture().sync(); + } finally { + group.shutdownGracefully().sync(); + } + } + + + public static void main(String[] args) throws Exception { + new TimerServer(65535).start(); // 启动 + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/TimerServerHandler.java b/p2p/src/test/java/org/platon/p2p/netty/TimerServerHandler.java new file mode 100644 index 0000000..3617eae --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/TimerServerHandler.java @@ -0,0 +1,26 @@ +package org.platon.p2p.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class TimerServerHandler extends SimpleChannelInboundHandler { + private int counter; + + public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception{ + System.out.println("Receive order :" + msg + ", the counter is : " + ++counter); + String currentTime = "QUERY TIME ORDER".equals(msg) ? new Date().toString() : "BAD ORDER"; + + currentTime = currentTime + System.getProperty("line.separator"); + + ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); + TimeUnit.SECONDS.sleep(1); + + System.out.println("send response timestamp: " + new Date()); + ctx.writeAndFlush(resp); + } +} \ No newline at end of file diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlaonClientHandler.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlaonClientHandler.java new file mode 100644 index 0000000..65bb2f6 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlaonClientHandler.java @@ -0,0 +1,54 @@ +package org.platon.p2p.netty.platon; + +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.P2pChannelHandler; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlaonClientHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LoggerFactory.getLogger(P2pChannelHandler.class); + + protected ByteString remoteNodeId; + + protected ByteString remoteNodePubKey; + + public PlaonClientHandler(ByteString remoteNodeId, ByteString remoteNodePubKey) { + super(); + this.remoteNodeId = remoteNodeId; + this.remoteNodePubKey = remoteNodePubKey; + } + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + if(remoteNodeId!=null) { + SessionManager sessionManager = SpringContextUtil.getBean("sessionManager"); + sessionManager.closeSession(remoteNodeId); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, PlatonMessage msg) throws Exception { + logger.debug("Received PlatonMessage:{}", msg); + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClient.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClient.java new file mode 100644 index 0000000..8d86eb1 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClient.java @@ -0,0 +1,126 @@ +package org.platon.p2p.netty.platon; + +import com.google.protobuf.ByteString; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultMessageSizeEstimator; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.platon.common.config.ConfigProperties; +import org.platon.common.config.NodeConfig; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.crypto.ECKey; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.NodeClient; +import org.platon.p2p.NodeContext; +import org.platon.p2p.common.CodecUtils; +import org.platon.p2p.common.PeerConfig; +import org.platon.p2p.common.PlatonMessageHelper; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.proto.session.SayHello; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class PlatonClient { + private static Logger logger = LoggerFactory.getLogger(NodeClient.class); + + private static EventLoopGroup workerGroup; + + public PlatonClient() { + workerGroup = new NioEventLoopGroup(0, new ThreadFactory() { + AtomicInteger cnt = new AtomicInteger(0); + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "NodeClientWorker-" + cnt.getAndIncrement()); + } + }); + } + + public boolean connect(ByteString remoteId, String host, int port, ByteString remoteNodePubKey){ + ChannelFuture channelFuture = connectAsync(remoteId, host, port, remoteNodePubKey).syncUninterruptibly(); + if(channelFuture.isDone() && channelFuture.isSuccess()){ + logger.debug("success to connect remote node (nodeId=:{}, address:={}:{}", CodecUtils.toHexString(remoteId), host, port); + + SessionManager sessionManager = SpringContextUtil.getBean("sessionManager"); + sessionManager.createSession(remoteId, remoteNodePubKey, channelFuture.channel()); + + PlatonMessage platonMessage = PlatonMessageHelper.createCreateSession(); + + + channelFuture.channel().writeAndFlush(platonMessage); + + return true; + }else{ + logger.error("cannot connect to remote node (nodeId=:{}, address:={}:{})", CodecUtils.toHexString(remoteId), host, port); + return false; + } + } + + public ChannelFuture connectAsync(ByteString remoteId,String host, int port, ByteString remoteNodePubKey) { + Bootstrap b = new Bootstrap(); + b.group(workerGroup); + b.channel(NioSocketChannel.class); + b.option(ChannelOption.SO_KEEPALIVE, true); + b.option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT); + b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, PeerConfig.getInstance().getPeerConnectTimeout()*1000); + b.remoteAddress(host, port); + b.handler(new PlatonClientChannelInitializer(remoteId, remoteNodePubKey)); + return b.connect(); + } + + public void shutdown() { + logger.info("Shutdown NodeClient"); + workerGroup.shutdownGracefully(); + workerGroup.terminationFuture().syncUninterruptibly(); + } + + public static void main(String[] args) throws Exception { + ConfigProperties.setConfigPath("D:\\workspace\\Juzix-Platon\\platon\\p2p\\src\\test\\resources\\config\\node2"); + NodeContext.host = NodeConfig.getInstance().getHost(); + NodeContext.port = PeerConfig.getInstance().getPort(); + NodeContext.privateKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPrivateKey()); + NodeContext.publicKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPublicKey()); + + NodeContext.ecKey = ECKey.fromPrivate(NodeContext.privateKey ); + NodeContext.localNodeId = ByteString.copyFrom(WalletUtil.computeAddress(NodeContext.publicKey)); + + NodeContext.timeIntervalForDuplicatedMessage = PeerConfig.getInstance().getTimeIntervalForDuplicatedMessage(); + ApplicationContext context = new AnnotationConfigApplicationContext(SpringContextUtil.class); + + PlatonClient client = new PlatonClient(); + byte[] serverPubKey = Numeric.hexStringToByteArray("0x044c73f76a65ada74e582fecfe576043657b61a71bdff3f21cf00e3a4f3a4a19212a7510efbe32c7d1f4d041b5cea3b1d0a5f3238af89f4f42b256b3d95702ec97"); + + ByteString serverId = ByteString.copyFrom(WalletUtil.computeAddress(serverPubKey)); + ByteString pubKey = ByteString.copyFrom(serverPubKey); + + client.connect(serverId, "127.0.0.1", 11001, pubKey); + + sayHello(serverId, "abc"); + TimeUnit.SECONDS.sleep(10); + } + + private static void sayHello(ByteString remoteNodeId, String hello) { + + NodeID nodeID = NodeID.newBuilder().setId(remoteNodeId).build(); + + SayHello sayHello = SayHello.newBuilder().setNodeId(NodeContext.localNodeId).setHello(hello).setFeedback(true).build(); + + MessageRouter messageRouter = SpringContextUtil.getBean("messageRouter"); + + + messageRouter.sendRequest(sayHello, nodeID, MessageRouter.ForwardingOptionType.DIRECT_CONNECTION, false); + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClientChannelInitializer.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClientChannelInitializer.java new file mode 100644 index 0000000..621e990 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonClientChannelInitializer.java @@ -0,0 +1,58 @@ +package org.platon.p2p.netty.platon; + +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.protobuf.ProtobufDecoder; +import io.netty.handler.codec.protobuf.ProtobufEncoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import org.platon.p2p.EccDecoder; +import org.platon.p2p.EccEncoder; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlatonClientChannelInitializer extends ChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(PlatonClientChannelInitializer.class); + + + private final static int MAX_FRAME_LENGTH = 1024*1024*1024; + private final static int LENGTH_FIELD = 4; + private final static int LENGTH_FIELD_OFFSET = 0; + private final static int LENGTH_ADJUSTMENT = 0; + private final static int INITIAL_BYTES_TO_STRIP = 4; + + + private ByteString remoteNodeId; + private ByteString remoteNodePubKey; + + public PlatonClientChannelInitializer(ByteString remoteNodeId, ByteString remoteNodePubKey) { + this.remoteNodeId = remoteNodeId; + this.remoteNodePubKey = remoteNodePubKey; + } + + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + logger.debug("init client channel ......"); + + + ChannelPipeline p = channel.pipeline(); + + + p.addLast(new EccDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)); + + p.addLast(new ProtobufVarint32FrameDecoder()); + p.addLast(new ProtobufDecoder(PlatonMessage.getDefaultInstance())); + + + p.addLast(new EccEncoder(remoteNodeId, remoteNodePubKey, LENGTH_FIELD)); + + p.addLast(new ProtobufVarint32LengthFieldPrepender()); + p.addLast(new ProtobufEncoder()); + + p.addLast(new PlaonClientHandler(remoteNodeId, remoteNodePubKey)); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServer.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServer.java new file mode 100644 index 0000000..ea7b3f4 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServer.java @@ -0,0 +1,89 @@ +package org.platon.p2p.netty.platon; + +import com.google.protobuf.ByteString; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import org.platon.common.config.ConfigProperties; +import org.platon.common.config.NodeConfig; +import org.platon.common.utils.Numeric; +import org.platon.common.utils.SpringContextUtil; +import org.platon.crypto.ECKey; +import org.platon.crypto.WalletUtil; +import org.platon.p2p.NodeContext; +import org.platon.p2p.NodeServer; +import org.platon.p2p.NodeServerChannelInitializer; +import org.platon.p2p.common.PeerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class PlatonServer { + private static final Logger logger = LoggerFactory.getLogger(NodeServer.class); + + + + private EventLoopGroup bossGroup = new NioEventLoopGroup(); + + + private EventLoopGroup workerGroup = new NioEventLoopGroup(); + + private void initContext(){ + NodeContext.host = NodeConfig.getInstance().getHost(); + NodeContext.port = PeerConfig.getInstance().getPort(); + NodeContext.privateKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPrivateKey()); + NodeContext.publicKey = Numeric.hexStringToByteArray(NodeConfig.getInstance().getPublicKey()); + + NodeContext.ecKey = ECKey.fromPrivate(NodeContext.privateKey ); + NodeContext.localNodeId = ByteString.copyFrom(WalletUtil.computeAddress(NodeContext.publicKey)); + + NodeContext.timeIntervalForDuplicatedMessage = PeerConfig.getInstance().getTimeIntervalForDuplicatedMessage(); + + ApplicationContext context = new AnnotationConfigApplicationContext(SpringContextUtil.class); + + } + + public void startup() { + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run() { + shutdown(); + } + }); + + + logger.info("starting Node server ..."); + + initContext(); + + ServerBootstrap bootstrap = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new NodeServerChannelInitializer()); + + ChannelFuture channelFuture = bootstrap.bind(NodeContext.host, NodeContext.port).syncUninterruptibly(); + + channelFuture.channel().closeFuture().addListener( new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + shutdown(); + } + }); + } + + private void shutdown() { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + + public static void main(String[] args) throws Exception { + ConfigProperties.setConfigPath("D:\\workspace\\Juzix-Platon\\platon\\p2p\\src\\test\\resources\\config\\node1"); + PlatonServer server1 = new PlatonServer(); + server1.startup(); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerChannelInitializer.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerChannelInitializer.java new file mode 100644 index 0000000..f98a7b6 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerChannelInitializer.java @@ -0,0 +1,50 @@ +package org.platon.p2p.netty.platon; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.protobuf.ProtobufDecoder; +import io.netty.handler.codec.protobuf.ProtobufEncoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import org.platon.p2p.EccDecoder; +import org.platon.p2p.EccEncoder; +import org.platon.p2p.NodeServerChannelInitializer; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlatonServerChannelInitializer extends ChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(NodeServerChannelInitializer.class); + + + private final static int MAX_FRAME_LENGTH = 1024*1024*1024; + private final static int LENGTH_FIELD = 4; + private final static int LENGTH_FIELD_OFFSET = 0; + private final static int LENGTH_ADJUSTMENT = 0; + private final static int INITIAL_BYTES_TO_STRIP = 4; + + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + logger.debug("init server channel ......"); + + + ChannelPipeline p = channel.pipeline(); + + + p.addLast("eccDecoder", new EccDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)); + + p.addLast(new ProtobufVarint32FrameDecoder()); + p.addLast(new ProtobufDecoder(PlatonMessage.getDefaultInstance())); + + + p.addLast("eccEncoder", new EccEncoder(LENGTH_FIELD)); + + p.addLast(new ProtobufVarint32LengthFieldPrepender()); + p.addLast(new ProtobufEncoder()); + + p.addLast("platonServerHandler", new PlatonServerHandler()); + + } +} diff --git a/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerHandler.java b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerHandler.java new file mode 100644 index 0000000..6a4af63 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/netty/platon/PlatonServerHandler.java @@ -0,0 +1,44 @@ +package org.platon.p2p.netty.platon; + +import com.google.protobuf.ByteString; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import org.platon.common.utils.SpringContextUtil; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.session.SessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlatonServerHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LoggerFactory.getLogger(PlatonServerHandler.class); + + protected ByteString remoteNodeId; + + public ByteString getRemoteNodeId() { + return remoteNodeId; + } + + public void setRemoteNodeId(ByteString remoteNodeId) { + this.remoteNodeId = remoteNodeId; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + if(remoteNodeId!=null) { + SessionManager sessionManager = SpringContextUtil.getBean("sessionManager"); + sessionManager.closeSession(remoteNodeId); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, PlatonMessage msg) throws Exception { + logger.debug("Received PlatonMessage:{}", msg); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/plugins/RoutingTableMock.java b/p2p/src/test/java/org/platon/p2p/plugins/RoutingTableMock.java new file mode 100644 index 0000000..7bf70d1 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/plugins/RoutingTableMock.java @@ -0,0 +1,136 @@ +package org.platon.p2p.plugins; + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.plugins.kademlia.Contact; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author yangzhou + * @create 2018-07-30 19:58 + */ +public class RoutingTableMock implements RoutingTable { + + private NodeID localNodeId = null; + + public void setNextHops(List nextHops) { + this.nextHops = nextHops; + } + + public List getNextHops() { + return nextHops; + } + + private List nextHops = null; + public RoutingTableMock(){ + } + + public RoutingTableMock(String id){ + NodeID nodeID = NodeID.newBuilder() + .setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(id))) + .build(); + + localNodeId = nodeID; + } + @Override + public NodeID getLocalNode() { + return localNodeId; + } + + @Override + public List getNextHops(NodeID destination) { + return null; + } + + @Override + public List getNextHops(NodeID destination, int num) { + return null; + } + + @Override + public List getNextHops(ResourceID destination) { + return null; + } + + @Override + public List getNextHops(ResourceID destination, int num) { + return null; + } + + @Override + public List getNextHops(RoutableID destination) { + Asserts.notNull(nextHops, "nextHops is null"); + return nextHops; + } + + @Override + public List getNextHops(RoutableID destination, int num) { + return new LinkedList<>(); + } + + @Override + public List getNeighbors() { + return new LinkedList<>(); + } + + @Override + public List getNeighbors(NodeID id, int num) { + return null; + } + + @Override + public List getNeighbors(ResourceID id, int num) { + return null; + } + + @Override + public List getNeighbors(RoutableID id, int num) { + return new LinkedList<>(); + } + + @Override + public List getAllNode() { + return new LinkedList<>(); + } + + @Override + public String getEndPoint(ByteString destination) { + return null; + } + + @Override + public NodeID getNodeID(ByteString destination) { + return null; + } + + + @Override + public void add(NodeID node) { + System.out.println("add node:" + NodeUtils.getNodeIdString(node.getId())); + } + + @Override + public void del(NodeID node) { + System.out.println("del node:" + NodeUtils.getNodeIdString(node.getId())); + } + + @Override + public String getTableToJson() { + return "123"; + } + + @Override + public void setTableFromJson(String json) { + } + + @Override + public List getAllContacts() { + return new LinkedList<>(); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/plugins/TestResourceMap.java b/p2p/src/test/java/org/platon/p2p/plugins/TestResourceMap.java new file mode 100644 index 0000000..fa96338 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/plugins/TestResourceMap.java @@ -0,0 +1,25 @@ +package org.platon.p2p.plugins; + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.junit.Test; +import org.platon.p2p.proto.common.ResourceID; + +/** + * @author yangzhou + * @create 2018-07-23 10:49 + */ +public class TestResourceMap { + @Test + public void TestAdd(){ + ResourceMap resourceMap = new ResourceMap(); + ResourceID resourceID = ResourceID.newBuilder().setId(ByteString.copyFrom("hello".getBytes())).build(); + ResourceID resourceID2 = ResourceID.newBuilder().setId(ByteString.copyFrom("hello1".getBytes())).build(); + + resourceMap.add(resourceID); + resourceMap.add(resourceID2); + + Asserts.check(resourceMap.isExist(resourceID), "is exist fail"); + Asserts.check(resourceMap.remove(resourceID), "is removeSessionFuture fail"); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubSubClusterTest.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubClusterTest.java new file mode 100644 index 0000000..e598745 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubClusterTest.java @@ -0,0 +1,254 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBMemoryImp; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.platon.p2p.router.MessageRouterMock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + + + + + + +@RunWith(PowerMockRunner.class) +public class PubSubClusterTest { + private static Logger logger = null; + + private static final String[] nodeStringList = {"e238a6077ffa4e93b943", "e238a6077ffa4e93b944", "e238a6077ffa4e93b945", "e238a6077ffa4e93b946", "e238a6077ffa4e93b947"}; + + MessageRouterMock messageRouter = null; + + DB db = null; + + + private PubSub pubSubA = null; + private PubSubSessionNotify notifyA = null; + private RoutingTableMock routingTableMockA = null; + + private PubSub pubSubB = null; + private PubSubSessionNotify notifyB = null; + private RoutingTableMock routingTableMockB = null; + + private PubSub pubSubC = null; + private PubSubSessionNotify notifyC = null; + private RoutingTableMock routingTableMockC = null; + + private PubSub pubSubD = null; + private PubSubSessionNotify notifyD = null; + private RoutingTableMock routingTableMockD = null; + + private PubSub pubSubE = null; + private PubSubSessionNotify notifyE = null; + private RoutingTableMock routingTableMockE = null; + + private List nodeList = null; + + @BeforeClass + public static void setLogger() throws MalformedURLException + { + logger = LoggerFactory.getLogger(PubsubTest.class); + } + + + @Before + public void init() throws Exception { + + + + initNodeList(); + + messageRouter = new MessageRouterMock(); + + db = new DBMemoryImp(); + + + + + + + + + } + + private void initPubSub() throws Exception { + PubSubRequestCallback pubSubRequestCallback = new PubSubRequestCallback(); + PubSubRequestCallback.ClusterCallback clusterCallback = pubSubRequestCallback.new ClusterCallback(); + + + routingTableMockA = new RoutingTableMock(nodeStringList[0]); + pubSubA = new PubsubMock(routingTableMockA, messageRouter); + clusterCallback.setPubSubMap(nodeStringList[0], pubSubA); + notifyA = new PubSubSessionNotify(pubSubA, nodeList.get(0)); + + routingTableMockB = new RoutingTableMock(nodeStringList[1]); + pubSubB = new PubsubMock(routingTableMockB, messageRouter); + clusterCallback.setPubSubMap(nodeStringList[1], pubSubB); + notifyB = new PubSubSessionNotify(pubSubB, nodeList.get(1)); + + + + + routingTableMockC = new RoutingTableMock(nodeStringList[2]); + pubSubC = new PubsubMock(routingTableMockC, messageRouter); + clusterCallback.setPubSubMap(nodeStringList[2], pubSubC); + notifyC = new PubSubSessionNotify(pubSubC, nodeList.get(2)); + + routingTableMockD = new RoutingTableMock(nodeStringList[3]); + pubSubD = new PubsubMock(routingTableMockD, messageRouter); + clusterCallback.setPubSubMap(nodeStringList[3], pubSubD); + notifyD = new PubSubSessionNotify(pubSubD, nodeList.get(3)); + + + routingTableMockE = new RoutingTableMock(nodeStringList[4]); + pubSubE = new PubsubMock(routingTableMockE, messageRouter); + + clusterCallback.setPubSubMap(nodeStringList[4], pubSubE); + notifyE = new PubSubSessionNotify(pubSubE, nodeList.get(4)); + + + notifyA.create(routingTableMockB.getLocalNode().getId()); + + notifyB.create(routingTableMockA.getLocalNode().getId()); + notifyB.create(routingTableMockC.getLocalNode().getId()); + + notifyC.create(routingTableMockB.getLocalNode().getId()); + notifyC.create(routingTableMockD.getLocalNode().getId()); + + notifyD.create(routingTableMockC.getLocalNode().getId()); + + notifyE.create(routingTableMockD.getLocalNode().getId()); + + messageRouter.addRequestCallback(ProtoBufHelper.getFullName(TopicMessage.class), clusterCallback); + } + + + + + private void initNodeList() { + nodeList = new ArrayList<>(); + for (String nodeString : nodeStringList) { + NodeID nodeId = NodeID.newBuilder().setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(nodeString))).build(); + nodeList.add(nodeId); + } + } + + + @Test + public void testSendMessage() throws Exception { + initPubSub(); + PubSubClusterCallbackMock pubSubClusterCallbackMockA = new PubSubClusterCallbackMock(nodeStringList[0]); + PubSubClusterCallbackMock pubSubClusterCallbackMockB = new PubSubClusterCallbackMock(nodeStringList[1]); + PubSubClusterCallbackMock pubSubClusterCallbackMockC = new PubSubClusterCallbackMock(nodeStringList[2]); + PubSubClusterCallbackMock pubSubClusterCallbackMockD = new PubSubClusterCallbackMock(nodeStringList[3]); + PubSubClusterCallbackMock pubSubClusterCallbackMockE = new PubSubClusterCallbackMock(nodeStringList[4]); + + + pubSubA.subscribe("hello", ProtoBufHelper.getFullName(TopicMessage.class), pubSubClusterCallbackMockA); + pubSubB.subscribe("hello", ProtoBufHelper.getFullName(TopicMessage.class), pubSubClusterCallbackMockB); + pubSubC.subscribe("hello", ProtoBufHelper.getFullName(TopicMessage.class), pubSubClusterCallbackMockC); + pubSubD.subscribe("hello", ProtoBufHelper.getFullName(TopicMessage.class), pubSubClusterCallbackMockD); + + Thread.sleep(100); + int sendCount = 200; + String prefix = "world+"; + for (int i = 0; i < sendCount; i++) { + + Thread.sleep(10); + String value = prefix+i; + try { + pubSubA.publish("hello", value.getBytes()); + } catch (Exception e) { + logger.error("publish:", e); + } + + } + + + Thread.sleep(1000); + + + Asserts.check(pubSubClusterCallbackMockA.subMessage.size() == sendCount, + "pubsubA receive count:" + pubSubClusterCallbackMockA.subMessage.size() + " expected:" + sendCount + + " lost:" + pubSubClusterCallbackMockA.findLost(0, sendCount, prefix)); + + Asserts.check(pubSubClusterCallbackMockB.subMessage.size() == sendCount, + "pubsubB receive count:" + pubSubClusterCallbackMockB.subMessage.size() + " expected:" + sendCount + + " lost:" + pubSubClusterCallbackMockB.findLost(0, sendCount, prefix)); + + Asserts.check(pubSubClusterCallbackMockC.subMessage.size() == sendCount, + "pubsubC receive count:" + pubSubClusterCallbackMockC.subMessage.size() + " expected:" + sendCount + + " lost:" + pubSubClusterCallbackMockC.findLost(0, sendCount, prefix)); + + Asserts.check(pubSubClusterCallbackMockD.subMessage.size() == sendCount, + "pubsubD receive count:" + pubSubClusterCallbackMockD.subMessage.size() + " expected:" + sendCount + + " lost:" + pubSubClusterCallbackMockD.findLost(0, sendCount, prefix)); + + pubSubE.subscribe("hello", ProtoBufHelper.getFullName(TopicMessage.class), pubSubClusterCallbackMockE); + + logger.trace("waiting......"); + CompletableFuture x = CompletableFuture.runAsync(()->{ + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + x.get(); + + logger.trace("pubsubeE:{}", pubSubClusterCallbackMockE.subMessage.size()); + Asserts.check(pubSubClusterCallbackMockE.subMessage.size() == sendCount, + "pubsubE receive count:" + pubSubClusterCallbackMockE.subMessage.size() + " expected:" + sendCount + + " lost:" + pubSubClusterCallbackMockE.findLost(0, sendCount, prefix)); + + } + + + + public class PubSubClusterCallbackMock implements PubSub.SubscribeCallback { + + Set subMessage = ConcurrentHashMap.newKeySet(); + String nodeId = null; + public PubSubClusterCallbackMock(String nodeId){ + this.nodeId = nodeId; + } + + public String findLost(int start, int end, String prefix) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < end; i++) { + String key = prefix +i; + if (!subMessage.contains(key)){ + stringBuilder.append(key); + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + @Override + public void subscribe(String topic, byte[] data) { + + logger.trace("{} receive new data topic:{} data:{}", nodeId, topic, new String(data)); + subMessage.add(new String(data)); + + } + } +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRequestCallback.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRequestCallback.java new file mode 100644 index 0000000..6b1c037 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRequestCallback.java @@ -0,0 +1,55 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.router.MessageRouterMock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * @author yangzhou + * @create 2018-08-02 16:44 + */ +public class PubSubRequestCallback { + private static Logger logger = LoggerFactory.getLogger(PubSubRouter.class); + public class ClusterCallback implements MessageRouterMock.RequestMessageCallback { + + Map pubSubMap = new HashMap<>(); + + public void setPubSubMap(String id, PubSub pubSub) { + pubSubMap.put(id, pubSub); + } + + @Override + public CompletableFuture sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn) { + Asserts.check(dest.size() == 1, "dest list size expected:1"); + + String id = NodeUtils.getNodeIdString(dest.get(0).getId()); + CompletableFuture.runAsync(()-> { + try { + logger.trace("ClusterCallback request nodeId:{}", id); + + PubSub pubSub = pubSubMap.get(id); + Asserts.notNull(pubSub, id + " pubsub is null"); + logger.trace("ClusterCallback request 2 nodeId:" + id); + pubSub.sendMessage((TopicMessage) msg, null); + }catch (Exception e) { + logger.error("nodeId:{} error:{}", id, e.getMessage()); + } + }).exceptionally(e->{ + logger.error("ERROR:" + e); + + return null;}); + return null; + } + } +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterMock.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterMock.java new file mode 100644 index 0000000..79477d4 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterMock.java @@ -0,0 +1,13 @@ +package org.platon.p2p.pubsub; + +import org.platon.p2p.plugins.RoutingTable; + +public class PubSubRouterMock extends PubSubRouter{ + + RoutingTable routingTableMock = null; + public PubSubRouterMock(RoutingTable routingTableMock) { + this.routingTableMock = routingTableMock; + setRoutingTable(routingTableMock); + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterTest.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterTest.java new file mode 100644 index 0000000..afe4454 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubRouterTest.java @@ -0,0 +1,195 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.platon.common.cache.DelayCache; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBMemoryImp; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.pubsub.EntryMessage; +import org.platon.p2p.proto.pubsub.SubMessage; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.platon.p2p.router.MessageRouterMock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-31 14:16 + */ +@RunWith(PowerMockRunner.class) +public class PubSubRouterTest { + + + private static Logger logger = null; + @InjectMocks + private PubSub pubSub = new PubSub(); + + + private MessageRouterMock messageRouter; + private RoutingTableMock routingTable; + private DB db; + + private PubSubSessionNotify notify; + + private PubSubRouter pubSubRouterMock; + private DelayCache msgCacheMock; + private Map> meshMock; + private Map> fanoutMock; + + private static final String[] nodeStringList = {"e238a6077ffa4e93b943", "e238a6077ffa4e93b944", "e238a6077ffa4e93b945", "e238a6077ffa4e93b946"}; + + private List nodeList; + + + @BeforeClass + public static void setLogger() + { + logger = LoggerFactory.getLogger(PubSubRouterTest.class); + } + + @Before + public void init(){ + //create other peers + initNodeList(); + + messageRouter = new MessageRouterMock(); + routingTable = new RoutingTableMock("e238a6077ffa4e93b943"); + db = new DBMemoryImp(); + pubSub = new PubsubMock(routingTable, messageRouter); + + mockPubSubRouter(); + + + + notify = new PubSubSessionNotify(pubSub, routingTable.getLocalNode()); + } + + private void initNodeList() { + nodeList = new ArrayList<>(); + for (String nodeString : nodeStringList) { + nodeList.add(Bytes.valueOf(NodeUtils.getNodeIdBytes(nodeString))); + } + } + + private void createSession() { + for (Bytes nodeid : nodeList) { + notify.create(ByteString.copyFrom(nodeid.getKey())); + } + } + + private void removeSession() { + for (Bytes nodeid : nodeList) { + notify.close(ByteString.copyFrom(nodeid.getKey())); + } + } + + + + private void mockPubSubRouter() { + try { + pubSubRouterMock = new PubSubRouter(); + pubSubRouterMock.setMessageRouter(messageRouter); + pubSubRouterMock.attach(pubSub); + + msgCacheMock = new DelayCache<>(); + meshMock = new ConcurrentHashMap<>(); + fanoutMock = new ConcurrentHashMap<>(); + + Field pubSubRouterField = pubSub.getClass().getDeclaredField("pubSubRouter"); + Field meshFiled = pubSubRouterMock.getClass().getDeclaredField("mesh"); + Field fanoutFiled = pubSubRouterMock.getClass().getDeclaredField("fanout"); + Field msgCacheFiled = pubSubRouterMock.getClass().getDeclaredField("msgCache"); + + pubSubRouterField.setAccessible(true); + meshFiled.setAccessible(true); + fanoutFiled.setAccessible(true); + msgCacheFiled.setAccessible(true); + + + meshFiled.set(pubSubRouterMock, meshMock); + fanoutFiled.set(pubSubRouterMock, fanoutMock); + msgCacheFiled.set(pubSubRouterMock, msgCacheMock); + pubSubRouterField.set(pubSub, pubSubRouterMock); + + } catch (Exception e) { + logger.error("error:", e); + } + } + + + + private void subscribe() { + List topicMessageList = new ArrayList<>(); + + + for (Bytes nodeId : nodeList) { + TopicMessage.Builder topicMessageBuilder = TopicMessage.newBuilder(); + topicMessageBuilder.setFromNodeId(ByteString.copyFrom(nodeId.getKey())); + SubMessage.Builder subMessageBuilder = SubMessage.newBuilder(); + subMessageBuilder.setTopic("hello"); + subMessageBuilder.setSub(true); + subMessageBuilder.setNodeId(ByteString.copyFrom(nodeId.getKey())); + + topicMessageBuilder.setSubscribe(subMessageBuilder); + + topicMessageList.add(topicMessageBuilder.build()); + } + for (TopicMessage topicMessage : topicMessageList) { + pubSub.sendMessage(topicMessage, null); + } + } + + @Test + public void testControlMessage() throws InterruptedException { + subscribe(); + + List topicMessageList = new ArrayList<>(); + for (Bytes nodeId : nodeList) { + TopicMessage.Builder topicMessageBuilder = TopicMessage.newBuilder(); + topicMessageBuilder.setFromNodeId(ByteString.copyFrom(nodeId.getKey())); + EntryMessage.Builder entryMessage = EntryMessage.newBuilder(); + entryMessage.setFromNodeId(ByteString.copyFrom(nodeId.getKey())); + entryMessage.setKey("123"); + entryMessage.setTopic("hello"); + entryMessage.setData(ByteString.copyFrom("world".getBytes())); + + topicMessageBuilder.addPublishedEntry(entryMessage); + + topicMessageList.add(topicMessageBuilder.build()); + } + + for (TopicMessage topicMessage : topicMessageList) { + pubSub.sendMessage(topicMessage, null); + } + + + Thread.sleep(2000); + Map> messageMap = messageRouter.getMessage(ProtoBufHelper.getFullName(TopicMessage.class)); + for (Map.Entry> x: messageMap.entrySet()) { + Asserts.check(x.getValue().size() == nodeList.size(), "send request size error, expected:" + nodeList.size()); + } + + } + +} + + diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubSubSubCallbackMock.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubSubCallbackMock.java new file mode 100644 index 0000000..ece12c7 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubSubSubCallbackMock.java @@ -0,0 +1,20 @@ +package org.platon.p2p.pubsub; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author yangzhou + * @create 2018-08-02 17:15 + */ + +public class PubSubSubCallbackMock implements PubSub.SubscribeCallback { + private static Logger logger = LoggerFactory.getLogger(PubSubSubCallbackMock.class); + + @Override + public void subscribe(String topic, byte[] data) { + logger.trace("receive new data:{}", topic); +// ServiceDiscoveryManager serviceDiscoveryManager = BeanLocator.getBean("serviceDiscoveryManager"); +// serviceDiscoveryManager.subscribe(topic, data); + } +} \ No newline at end of file diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubsubMock.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubsubMock.java new file mode 100644 index 0000000..549515a --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubsubMock.java @@ -0,0 +1,32 @@ +package org.platon.p2p.pubsub; + + +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.session.SessionNotify; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PubsubMock extends PubSub{ + private static Logger logger = LoggerFactory.getLogger(PubsubMock.class); + + + + public PubsubMock(RoutingTable routingTableMock, MessageRouter messageRouter){ + this.pubSubRouter = new PubSubRouterMock(routingTableMock); + this.setRoutingTable(routingTableMock); + this.setMessageRouter(messageRouter); + this.pubSubRouter.setMessageRouter(messageRouter); + this.pubSubRouter.attach(this); + //logger.debug("add sessionNotify"); + try { + SessionNotify.addListener(new PubSubSessionNotify(this, routingTableMock.getLocalNode())); + } catch (Exception e) { + //logger.error(e); + } + } + + + + +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/PubsubTest.java b/p2p/src/test/java/org/platon/p2p/pubsub/PubsubTest.java new file mode 100644 index 0000000..a237655 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/PubsubTest.java @@ -0,0 +1,214 @@ +package org.platon.p2p.pubsub; + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBMemoryImp; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.proto.pubsub.SubMessage; +import org.platon.p2p.proto.pubsub.TopicMessage; +import org.platon.p2p.router.MessageRouterMock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author yangzhou + * @create 2018-07-30 16:47 + */ + +@RunWith(PowerMockRunner.class) +public class PubsubTest { + + private static Logger logger = null; + @InjectMocks + private PubsubMock pubSub = null; + + + MessageRouterMock messageRouter = null; + RoutingTableMock routingTable = null; + DB db = null; + + PubSubSessionNotify notify = null; + + Set peersMock = null; + Map> myTopicsMock = null; + Map> topicsMock = null; + + + private static final String[] nodeStringList = {"e238a6077ffa4e93b943", "e238a6077ffa4e93b944", "e238a6077ffa4e93b945", "e238a6077ffa4e93b946"}; + + private List nodeList = null; + + + @BeforeClass + public static void setLogger() throws MalformedURLException + { + logger = LoggerFactory.getLogger(PubsubTest.class); + } + + @Before + public void init(){ + //create other peers + initNodeList(); + + messageRouter = new MessageRouterMock(); + routingTable = new RoutingTableMock("e238a6077ffa4e93b942"); + db = new DBMemoryImp(); + pubSub = new PubsubMock(routingTable, messageRouter); + + //mock PubSub's members + mockPeers(); + mockMyTopics(); + mockTopic(); + + + notify = new PubSubSessionNotify(pubSub, routingTable.getLocalNode()); + } + + private void initNodeList() { + nodeList = new ArrayList<>(); + for (String nodeString : nodeStringList) { + nodeList.add(Bytes.valueOf(NodeUtils.getNodeIdBytes(nodeString))); + } + } + + + private void mockPeers() { + try { + Field peersField = pubSub.getClass().getSuperclass().getDeclaredField("peers"); + peersMock = new HashSet<>(); + peersField.setAccessible(true); + + peersField.set(pubSub, peersMock); + } catch (Exception e) { + logger.error("error:", e); + } + } + + private void mockMyTopics() { + try { + Field peersField = pubSub.getClass().getSuperclass().getDeclaredField("mytopics"); + myTopicsMock = new HashMap<>(); + peersField.setAccessible(true); + + peersField.set(pubSub, myTopicsMock); + } catch (Exception e) { + logger.error("error:", e); + } + } + + private void mockTopic() { + try { + Field peersField = pubSub.getClass().getSuperclass().getDeclaredField("topics"); + topicsMock = new ConcurrentHashMap<>(); + peersField.setAccessible(true); + + peersField.set(pubSub, topicsMock); + } catch (Exception e) { + logger.error("error:", e); + } + } + + private void createSession() { + for (Bytes nodeid : nodeList) { + notify.create(ByteString.copyFrom(nodeid.getKey())); + } + } + + private void removeSession() { + for (Bytes nodeid : nodeList) { + notify.close(ByteString.copyFrom(nodeid.getKey())); + + } + } + + + @Test + public void testSessionNotify() { + createSession(); + Set createdPeers = peersMock; + Asserts.check(createdPeers.size() == nodeList.size(), "create session error peers size error expected:" + nodeList.size()); + + for (Bytes nodeId : nodeList) { + Asserts.check(createdPeers.contains(nodeId), "pubsub not contains " + nodeId); + } + + removeSession(); + + Asserts.check(createdPeers.size() == 0, "create session error peers size error expected:0"); + + for (Bytes nodeId : nodeList) { + Asserts.check(!createdPeers.contains(nodeId), "pubsub not contains " + nodeId); + } + } + + + @Test + public void testSubscribe() throws InterruptedException { + createSession(); + pubSub.subscribe("hello", "world", null); + + Thread.sleep(1000); + int count = messageRouter.getRequestCount(ProtoBufHelper.getFullName(TopicMessage.class)); + + Asserts.check( count == nodeList.size(), + "send subscribe request count error count:" + count + " , expected:"+nodeList.size()); + messageRouter.clearRequest(ProtoBufHelper.getFullName(TopicMessage.class)); + removeSession(); + } + + + + @Test + public void testSendMessageSubscribe() { + createSession(); + + List topicMessageList = new ArrayList<>(); + + for (Bytes nodeID : nodeList) { + TopicMessage.Builder topicMessageBuilder = TopicMessage.newBuilder(); + topicMessageBuilder.setFromNodeId(ByteString.copyFrom(nodeID.getKey())); + SubMessage.Builder subMessageBuilder = SubMessage.newBuilder(); + subMessageBuilder.setTopic("hello"); + subMessageBuilder.setSub(true); + subMessageBuilder.setNodeId(ByteString.copyFrom(nodeID.getKey())); + + topicMessageBuilder.setSubscribe(subMessageBuilder); + + topicMessageList.add(topicMessageBuilder.build()); + } + for (TopicMessage topicMessage : topicMessageList) { + pubSub.sendMessage(topicMessage, null); + } + + Set nodeIDSet = topicsMock.get("hello"); + for (Bytes nodeId : nodeList) { + Asserts.check(nodeIDSet.contains(nodeId), "topics error expected:" + nodeId); + } + } + + + @Test + public void testControlMessage(){ + + + } + + + + +} diff --git a/p2p/src/test/java/org/platon/p2p/pubsub/TimeCacheTest.java b/p2p/src/test/java/org/platon/p2p/pubsub/TimeCacheTest.java new file mode 100644 index 0000000..606e44d --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/pubsub/TimeCacheTest.java @@ -0,0 +1,36 @@ +package org.platon.p2p.pubsub; + +import org.apache.http.util.Asserts; +import org.junit.Test; + +/** + * @author yangzhou + * @create 2018-07-31 13:47 + */ +public class TimeCacheTest { + + @Test + public void testTimeCache() throws InterruptedException { + TimeCache timeCache = new TimeCache(1000); + + for (int i = 0; i < 1000; i++) { + timeCache.add(String.valueOf(i)); + } + + Thread.sleep(1001); + timeCache.sweep(); + + for (int i = 0; i < 1000; i++) { + Asserts.check(!timeCache.has(String.valueOf(i)), "has expired element:" + i); + } + + for (int i = 1001; i < 2000; i++) { + timeCache.add(String.valueOf(i)); + } + + for (int i = 1001; i < 2000; i++) { + Asserts.check(timeCache.has(String.valueOf(i)), "no has element:" + i); + + } + } +} diff --git a/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterNode.java b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterNode.java new file mode 100644 index 0000000..fa0f231 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterNode.java @@ -0,0 +1,27 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.db.DB; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.pubsub.PubSub; +import org.platon.p2p.pubsub.PubSubSessionNotify; +import org.platon.p2p.router.MessageRouterMock; + + +public class ReDiRClusterNode { + ReDiRClusterNode(PubSub pubSub, DB db, MessageRouterMock messageRouter, + ServiceDiscoveryManager serviceDiscoveryManager, + PubSubSessionNotify notify, RoutingTableMock routingTable) { + this.pubSub = pubSub; + this.db = db; + this.messageRouter = messageRouter; + this.serviceDiscoveryManager = serviceDiscoveryManager; + this.notify = notify; + this.routingTable = routingTable; + } + PubSubSessionNotify notify; + PubSub pubSub; + DB db; + MessageRouterMock messageRouter; + ServiceDiscoveryManager serviceDiscoveryManager; + RoutingTableMock routingTable; +} \ No newline at end of file diff --git a/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterRequestCallback.java b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterRequestCallback.java new file mode 100644 index 0000000..7873db8 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterRequestCallback.java @@ -0,0 +1,130 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.Any; +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Body; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; +import org.platon.p2p.proto.redir.ReDiRFindMessage; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.router.MessageRouterMock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +public class ReDiRClusterRequestCallback { + private static Logger logger = LoggerFactory.getLogger(ReDiRClusterRequestCallback.class); + + private static Map> futureMap = new ConcurrentHashMap<>(); + + private ReDiRClusterNode self = null; + + private Map clusterNodeMap = new HashMap<>(); + + public ReDiRClusterRequestCallback(ReDiRClusterNode self, List reDiRClusterNodeList) { + this.self = self; + for (ReDiRClusterNode reDiRClusterNode : reDiRClusterNodeList) { + clusterNodeMap.put(Bytes.valueOf(reDiRClusterNode.routingTable.getLocalNode().getId()), reDiRClusterNode); + } + } + + public static void addFuture(String transactionId, CompletableFuture future) { + futureMap.put(transactionId, future); + } + + public static CompletableFuture getFuture(String transactionId) { + return futureMap.get(transactionId); + } + + + public class ClusterPublishAndFindCallback implements MessageRouterMock.RequestMessageCallback { + + + @Override + public CompletableFuture sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn) { + Asserts.check(dest.size() == 1, "dest list size expected:1"); + + logger.trace("sendRequest localnode:{}", NodeUtils.getNodeIdString(self.routingTable.getLocalNode().getId())); + + + String transactionId = UUID.randomUUID().toString(); + + + Header.Builder header = Header.newBuilder().addAllDest(dest).setTxId(transactionId).addVia(self.routingTable.getLocalNode()); + + + List peerList = self.routingTable.getNextHops( dest.get(0)); + + for (NodeID nodeId : peerList) { + clusterNodeMap.get(nodeId).messageRouter.sendFrowardRequest(msg, dest, type, isReturn, HeaderHelper.build(header.build())); + } + + CompletableFuture future = new CompletableFuture<>(); + + futureMap.put(transactionId, future); + + return future; + } + } + + + public class ClusterForwardRequestCallback implements MessageRouterMock.ForwardRequestMessageCallback { + + @Override + public void sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn, HeaderHelper header) { + + + PlatonMessage platonMessage = PlatonMessage.newBuilder() + .setBody(Body.newBuilder().setData(Any.pack(msg))) + .setHeader(header.getHeader()).build(); + + + if (self.serviceDiscoveryManager.reDiRMessageHook.isNeedProcess(platonMessage) || self.routingTable.getNextHops() == null) { + CompletableFuture.runAsync(()-> { + if (ProtoBufHelper.getFullName(msg.getClass()).compareTo("ReDiRMessage") == 0) { + self.serviceDiscoveryManager.publish((ReDiRMessage) msg, header); + } else { + self.serviceDiscoveryManager.discovery((ReDiRFindMessage) msg, header); + } + }); + } else { + List nextHops = self.serviceDiscoveryManager.reDiRForwardMessageHook.nextHops(header.getHeader(), Any.pack(msg)); + if (nextHops == null || nextHops.isEmpty()) { + nextHops = self.serviceDiscoveryManager.routingTable.getNextHops(dest.get(0)); + } + for (NodeID nodeId : nextHops) { + CompletableFuture.runAsync(()-> { + clusterNodeMap.get(nodeId).messageRouter.sendFrowardRequest(msg, dest, type, isReturn, header); + }); + } + } + } + } + + public class ClusterResponseCallback implements MessageRouterMock.ResponseMessageCallback { + + @Override + public void sendResponse(Message msg, String transactionID, List dest, MessageRouter.ForwardingOptionType type) { + + CompletableFuture future = futureMap.get(transactionID); + Asserts.notNull(future, "traunasaction nodeId: " + transactionID + " future is null"); + future.complete(msg); + } + + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterTest.java b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterTest.java new file mode 100644 index 0000000..b420fd8 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ReDiRClusterTest.java @@ -0,0 +1,158 @@ +package org.platon.p2p.redir; + + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBMemoryImp; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.redir.ReDiRFindMessage; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.proto.redir.ServiceEntry; +import org.platon.p2p.pubsub.PubSub; +import org.platon.p2p.pubsub.PubSubSessionNotify; +import org.platon.p2p.pubsub.PubsubMock; +import org.platon.p2p.router.MessageRouterMock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@RunWith(PowerMockRunner.class) +public class ReDiRClusterTest { + private static Logger logger = null; + private static final String[] nodeStringList = {"e238a6077ffa4e93b943", "e238a6077ffa4e93b944", "e238a6077ffa4e93b945", "e238a6077ffa4e93b946", "e238a6077ffa4e93b947"}; + private List nodeList = null; + + MessageRouterMock messageRouter = null; + + + + private List rediRClusterNodeList = new LinkedList<>(); + + @BeforeClass + public static void setLogger() throws MalformedURLException + { + logger = LoggerFactory.getLogger(ReDiRClusterTest.class); + } + + @Before + public void init() { + + + + initNodeList(); + + messageRouter = new MessageRouterMock(); + } + + @Test + public void testReDiRCluster() throws InterruptedException, ExecutionException { + initCluster(); + + + for (int i = rediRClusterNodeList.size()-2; i >= 0; i--) { + rediRClusterNodeList.get(i).notify.create(rediRClusterNodeList.get(i+1).routingTable.getLocalNode().getId()); + rediRClusterNodeList.get(i).routingTable.setNextHops(Collections.singletonList(rediRClusterNodeList.get(i+1).routingTable.getLocalNode())); + } + + + for (int i = 0; i < rediRClusterNodeList.size(); i++) { + ReDiRClusterRequestCallback reDiRClusterRequestCallback = new ReDiRClusterRequestCallback(rediRClusterNodeList.get(i), rediRClusterNodeList); + ReDiRClusterRequestCallback.ClusterPublishAndFindCallback clusterPublishAndFindCallback = reDiRClusterRequestCallback.new ClusterPublishAndFindCallback(); + ReDiRClusterRequestCallback.ClusterForwardRequestCallback clusterForwardRequestCallback = reDiRClusterRequestCallback.new ClusterForwardRequestCallback(); + ReDiRClusterRequestCallback.ClusterResponseCallback clusterResponseCallback = reDiRClusterRequestCallback.new ClusterResponseCallback(); + + + rediRClusterNodeList.get(i).messageRouter.addRequestCallback(ProtoBufHelper.getFullName(ReDiRMessage.class), clusterPublishAndFindCallback); + rediRClusterNodeList.get(i).messageRouter.addRequestCallback(ProtoBufHelper.getFullName(ReDiRFindMessage.class), clusterPublishAndFindCallback); + rediRClusterNodeList.get(i).messageRouter.addForwardRequestCallback(ProtoBufHelper.getFullName(ReDiRMessage.class), clusterForwardRequestCallback); + rediRClusterNodeList.get(i).messageRouter.addForwardRequestCallback(ProtoBufHelper.getFullName(ReDiRFindMessage.class), clusterForwardRequestCallback); + rediRClusterNodeList.get(i).messageRouter.addResponseCallback(ProtoBufHelper.getFullName(ReDiRFindMessage.class), clusterResponseCallback); + } + + + + + + ServiceEntry entry = ServiceEntry.newBuilder() + .setServiceType("hashrate") + .setDescribe("hello world") + .setSourceKey("64") + .setFrom("3056301006072a8648c9") + .setUrl("www.platon.network").build(); + + rediRClusterNodeList.get(0).serviceDiscoveryManager.publish(entry, false); + + Thread.sleep(1000); + + + CompletableFuture> future = rediRClusterNodeList.get(0).serviceDiscoveryManager.find("hashrate", "64"); + + List entryList = future.get(); + + Asserts.check(entryList.size() == 1, ""); + } + + private void initCluster() { + for (String nodeString : nodeStringList) { + + NodeID nodeId = NodeID.newBuilder() + .setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(nodeString))) + .build(); + nodeList.add(nodeId); + RoutingTableMock routingTableMock = new RoutingTableMock(nodeString); + + MessageRouterMock messageRouterMock = new MessageRouterMock(); + PubsubMock pubsubMock = new PubsubMock(routingTableMock, messageRouterMock); + DB db = new DBMemoryImp(); + ServiceDiscoveryManager serviceDiscoveryManagerMock = + new ServiceDiscoverManagerMock(pubsubMock, messageRouterMock, db, routingTableMock); + + PubSubSessionNotify notify = new PubSubSessionNotify(pubsubMock, nodeId); + ReDiRClusterNode rediRClusterNode = new ReDiRClusterNode(pubsubMock, db, messageRouterMock, serviceDiscoveryManagerMock, notify, routingTableMock); + rediRClusterNodeList.add(rediRClusterNode); + } + } + + + private void initNodeList() { + nodeList = new ArrayList<>(); + for (String nodeString : nodeStringList) { + NodeID nodeId = NodeID.newBuilder() + .setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(nodeString))) + .build(); + nodeList.add(nodeId); + } + } + + + public class ReDiRClusterSubscribeCallbackMock implements PubSub.SubscribeCallback { + + String nodeId = null; + public ReDiRClusterSubscribeCallbackMock(String nodeId){ + this.nodeId = nodeId; + } + + + @Override + public void subscribe(String topic, byte[] data) { + + logger.trace("{} receive new data topic:{} data:{}", nodeId, topic, new String(data)); + } + } +} diff --git a/p2p/src/test/java/org/platon/p2p/redir/ReDiRRequestCallback.java b/p2p/src/test/java/org/platon/p2p/redir/ReDiRRequestCallback.java new file mode 100644 index 0000000..ab7bcf8 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ReDiRRequestCallback.java @@ -0,0 +1,105 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.redir.ReDiRFindRespMessage; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.proto.redir.ReDiRRespMessage; +import org.platon.p2p.proto.redir.ServiceEntry; +import org.platon.p2p.router.MessageRouter; +import org.platon.p2p.router.MessageRouterMock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + + +public class ReDiRRequestCallback { + private static Logger logger = LoggerFactory.getLogger(ReDiRRequestCallback.class); + public class PublishCallback implements MessageRouterMock.RequestMessageCallback { + + + + Map> resourceIDListMap = new HashMap<>(); + + public PublishCallback(){ + + } + + public void setKeyList(Bytes resourceId, List currentKeyList) { + resourceIDListMap.put(resourceId, currentKeyList); + } + + @Override + public CompletableFuture sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn) { + logger.trace("request message:{}", msg.toString()); + + Asserts.check(dest.size() == 1, "dest list size error, expected:1"); + Asserts.check(dest.get(0).getType() == RoutableID.DestinationType.RESOURCEIDTYPE, "dest list nodeId error, expected:ResourceID"); + + ReDiRRespMessage keyMsg = ReDiRRespMessage.newBuilder().build(); + List keyList = new ArrayList<>(); + keyList.add(((ReDiRMessage)msg).getEntry().getSourceKey()); + if (resourceIDListMap.get(dest.get(0)) != null) { + keyList.addAll(resourceIDListMap.get(Bytes.valueOf(dest.get(0).getId()))); + } + + keyMsg.toBuilder().addAllKey(keyList); + CompletableFuture future = new CompletableFuture<>(); + future.complete(keyMsg); + return future; + } + } + + public class FindCallback implements MessageRouterMock.RequestMessageCallback { + + Map> resourceIDListMap = new HashMap<>(); + Map> resourceIDEntryListMap = new HashMap<>(); + + public FindCallback(){ + + } + + public void setKeyList(Bytes resourceId, List currentKeyList) { + resourceIDListMap.put(resourceId, currentKeyList); + } + + public void setEntryList(Bytes resourceId, List entryList) { + resourceIDEntryListMap.put(resourceId, entryList); + } + + @Override + public CompletableFuture sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn) { + logger.trace("request message:{}", msg.toString()); + + Asserts.check(dest.size() == 1, "dest list size error, expected:1"); + Asserts.check(dest.get(0).getType() == RoutableID.DestinationType.RESOURCEIDTYPE, "dest list nodeId error, expected:ResourceID"); + + ReDiRFindRespMessage reDiRFindListMessage = ReDiRFindRespMessage.newBuilder().build(); + + reDiRFindListMessage.toBuilder().setResourceId(ResourceID.newBuilder().setId(dest.get(0).getId())); + + + + List mockEntryList = resourceIDEntryListMap.get(reDiRFindListMessage.getResourceId()); + List mockKeyList = resourceIDListMap.get(reDiRFindListMessage.getResourceId()); + if (mockEntryList != null) { + reDiRFindListMessage.toBuilder().addAllEntry(mockEntryList); + } else { + reDiRFindListMessage.toBuilder().addAllKey(mockKeyList); + } + + CompletableFuture future = new CompletableFuture<>(); + future.complete(reDiRFindListMessage); + return future; + } + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerMock.java b/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerMock.java new file mode 100644 index 0000000..4912148 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerMock.java @@ -0,0 +1,24 @@ +package org.platon.p2p.redir; + +import org.platon.p2p.db.DB; +import org.platon.p2p.plugins.RoutingTable; +import org.platon.p2p.pubsub.PubSub; +import org.platon.p2p.router.MessageRouter; + +public class ServiceDiscoverManagerMock extends ServiceDiscoveryManager { + + ServiceDiscoverManagerMock(PubSub pubSub, MessageRouter messageRouter, DB db, RoutingTable routingTable){ + this.pubSub = pubSub; + this.messageRouter = messageRouter; + this.db = db; + this.reDiR = new ReDiR(); + this.routingTable = routingTable; + + + reDiR.pubSub = pubSub; + reDiR.db = db; + reDiR.messageRouter = messageRouter; + + } + +} diff --git a/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerTest.java b/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerTest.java new file mode 100644 index 0000000..ee816d6 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/redir/ServiceDiscoverManagerTest.java @@ -0,0 +1,254 @@ +package org.platon.p2p.redir; + +import com.google.protobuf.ByteString; +import org.apache.http.util.Asserts; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.platon.common.cache.DelayCache; +import org.platon.p2p.common.Bytes; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.db.DB; +import org.platon.p2p.db.DBMemoryImp; +import org.platon.p2p.plugins.RoutingTableMock; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.ResourceID; +import org.platon.p2p.proto.pubsub.EntryMessage; +import org.platon.p2p.proto.redir.ReDiRMessage; +import org.platon.p2p.proto.redir.ServiceEntry; +import org.platon.p2p.pubsub.*; +import org.platon.p2p.router.MessageRouterMock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + + + +@RunWith(PowerMockRunner.class) +@PrepareForTest({KeySelectorFactory.class}) +public class ServiceDiscoverManagerTest { + + private static Logger logger = null; + private ServiceDiscoveryManager serviceDiscoveryManager = null; + + + private PubSub pubSub; + + + + private MessageRouterMock messageRouter = null; + private RoutingTableMock routingTable = null; + private DB db = null; + + + private PubSubSessionNotify notify = null; + + private PubSubRouter pubSubRouterMock = null; + private DelayCache msgCacheMock = null; + private Map> meshMock = null; + private Map> fanoutMock = null; + + private static final String[] nodeStringList = {"e238a6077ffa4e93b943", "e238a6077ffa4e93b944", "e238a6077ffa4e93b945", "e238a6077ffa4e93b946"}; + + private List nodeList = null; + + + @BeforeClass + public static void setLogger() throws MalformedURLException + { + logger = LoggerFactory.getLogger(PubSubRouterTest.class); + } + + @Before + public void init(){ + + initNodeList(); + + messageRouter = new MessageRouterMock(); + routingTable = new RoutingTableMock("3056301006072a8648c9"); + db = new DBMemoryImp(); + + + KeySelector selector = new KeySelector.Builder(). + branchingFactor(4). + namespace("hashrate"). + level(4). + lowestKey(BigInteger.valueOf(0)). + highestKey(BigInteger.valueOf(1024)). + startLevel(2). + keyAlgorithmFunction(KeyAlgorithm.get("hashrate")).build(); + + + PowerMockito.mockStatic(KeySelectorFactory.class); + + pubSub = new PubsubMock(routingTable, messageRouter); + + + + + + + serviceDiscoveryManager = new ServiceDiscoverManagerMock(pubSub, messageRouter, db, routingTable); + + PowerMockito.when(KeySelectorFactory.get("hashrate")).thenReturn(selector); + + } + + private void initNodeList() { + nodeList = new ArrayList<>(); + for (String nodeString : nodeStringList) { + NodeID nodeId = NodeID.newBuilder() + .setId(ByteString.copyFrom(NodeUtils.getNodeIdBytes(nodeString))).build(); + nodeList.add(nodeId); + } + } + + private void createSession() { + for (NodeID nodeid : nodeList) { + notify.create(nodeid.getId()); + } + } + + private void removeSession() { + for (NodeID nodeid : nodeList) { + notify.close(nodeid.getId()); + } + } + + + + @Test + public void testPublish() { + ReDiRRequestCallback reDiRRequestCallback = new ReDiRRequestCallback(); + ReDiRRequestCallback.PublishCallback publishCallback = reDiRRequestCallback.new PublishCallback(); + ServiceEntry entry = ServiceEntry.newBuilder() + .setServiceType("hashrate") + .setDescribe("hello world") + .setSourceKey("64") + .setFrom("3056301006072a8648c9") + .setUrl("www.platon.network") + .build(); + + BigInteger key = new BigInteger("64"); + System.out.println("---------------------------------------------::::: " + key.intValue()); + KeySelector selector = KeySelectorFactory.get("hashrate"); + + messageRouter.addRequestCallback(ProtoBufHelper.getFullName(ReDiRMessage.class), publishCallback); + + serviceDiscoveryManager.publish(entry, false); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + + Integer count = messageRouter.getRequestCount(ProtoBufHelper.getFullName(ReDiRMessage.class)); + + Asserts.check( count== 4, "receive redir publish message error received count:" + count + " expected count:4"); + } + + + private void createKeyList(ReDiRRequestCallback.FindCallback callback, KeySelector selector, String sourceKey) { + ResourceID resourceId = selector.getResourceID(selector.getStartLevel(), sourceKey); + List keyList = new ArrayList<>(); + keyList.add("0"); + keyList.add("128"); + keyList.add("256"); + keyList.add("512"); + callback.setKeyList(Bytes.valueOf(resourceId.getId()), keyList); + + ResourceID resourceId2 = selector.getResourceID(selector.getStartLevel()-1, sourceKey); + + List keyList2 = new ArrayList<>(); + keyList2.add("0"); + keyList2.add("53"); + keyList2.add("47"); + keyList2.add("68"); + callback.setKeyList(Bytes.valueOf(resourceId2.getId()), keyList2); + + ResourceID resourceId3 = selector.getResourceID(selector.getStartLevel()+1, sourceKey); + + List keyList3 = new ArrayList<>(); + keyList3.add("0"); + keyList3.add("53"); + keyList3.add("47"); + keyList3.add("68"); + callback.setKeyList(Bytes.valueOf(resourceId3.getId()), keyList3); + } + + private void createEntryList(ReDiRRequestCallback.FindCallback callback, KeySelector selector, String sourceKey) { + ResourceID resourceId = selector.getResourceID(selector.getStartLevel()+1, sourceKey); + List entryList = new ArrayList<>(); + + + + ServiceEntry entry = ServiceEntry.newBuilder() + .setServiceType("hashrate") + .setDescribe("hello world") + .setSourceKey("20") + .setFrom("3056301006072a8648c9") + .setUrl("www.platon.network") + .build(); + + + ServiceEntry entry2 = ServiceEntry.newBuilder() + .setServiceType("hashrate") + .setDescribe("hello world") + .setSourceKey("64") + .setFrom("3056301006072a8648ca") + .setUrl("www.platon.network") + .build(); + + entryList.add(entry); + entryList.add(entry2); + + callback.setEntryList(Bytes.valueOf(resourceId.getId()), entryList); + } + + @Test + public void testFind() throws ExecutionException, InterruptedException { + KeySelector selector = KeySelectorFactory.get("hashrate"); + + String sourceKey = "64"; + + ReDiRRequestCallback reDiRRequestCallback = new ReDiRRequestCallback(); + ReDiRRequestCallback.FindCallback findCallback = reDiRRequestCallback.new FindCallback(); + + createKeyList(findCallback, selector, sourceKey); + createEntryList(findCallback, selector, sourceKey); + + + messageRouter.addRequestCallback(ProtoBufHelper.getFullName(ReDiRMessage.class), findCallback); + + CompletableFuture> future= serviceDiscoveryManager.find("hashrate", sourceKey); + + List entryList = future.get(); + + Asserts.notNull(entryList, "find error entry list is null"); + + Integer count = messageRouter.getRequestCount(ProtoBufHelper.getFullName(ReDiRMessage.class)); + + Asserts.check( count== 2, "receive redir discovery message error" + "received count:" + count + "expected count:4"); + } + + + + + +} diff --git a/p2p/src/test/java/org/platon/p2p/router/MessageRouterMock.java b/p2p/src/test/java/org/platon/p2p/router/MessageRouterMock.java new file mode 100644 index 0000000..d76f38d --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/router/MessageRouterMock.java @@ -0,0 +1,191 @@ +package org.platon.p2p.router; + +import com.google.protobuf.Message; +import org.apache.http.util.Asserts; +import org.platon.p2p.common.HeaderHelper; +import org.platon.p2p.common.NodeUtils; +import org.platon.p2p.common.ProtoBufHelper; +import org.platon.p2p.proto.common.RoutableID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + + +public class MessageRouterMock extends MessageRouter { + + Logger logger = LoggerFactory.getLogger(MessageRouterMock.class); + + public final Map requestCount = new HashMap<>(); + + private final Map>> requestMessage = new HashMap<>(); + + private final Map requestMessageCallback = new HashMap<>(); + private final Map responseMessageCallback = new HashMap<>(); + private final Map forwardRequestMessageCallbackHashMap = new HashMap<>(); + + public void addForwardRequestCallback(String methodId, ForwardRequestMessageCallback callback) { + forwardRequestMessageCallbackHashMap.put(methodId, callback); + } + + public void addRequestCallback(String methodId, RequestMessageCallback callback) { + requestMessageCallback.put(methodId, callback); + } + + public void addResponseCallback(String methodId, ResponseMessageCallback callback) { + responseMessageCallback.put(methodId, callback); + } + + public String methodId(Message msg) { + return ProtoBufHelper.getFullName(msg.getClass()); + } + + public void clearRequest(String methodId) { + + synchronized (requestCount) { + requestCount.remove(methodId); + requestCount.put(methodId, 0); + } + } + + public Integer getRequestCount(String methodId) { + Integer count = null; + synchronized (requestCount) { + count = requestCount.get(methodId); + } + return count == null ? 0 : count; + } + + public void dumpRequestMessage(){ + for (Map.Entry>> m : requestMessage.entrySet()) { + logger.trace("method:{}" , m.getKey()); + for (Map.Entry> r : m.getValue().entrySet()) { + logger.trace("RoutableID:{}", NodeUtils.getNodeIdString(r.getKey().getId().toByteArray())); + for (Message message : r.getValue()) { + logger.trace("message:{}", message.toString()); + } + } + } + } + + public Map> getMessage(String methodId){ + return requestMessage.get(methodId); + } + + @Override + public CompletableFuture sendRequest(Message msg, List dest, ForwardingOptionType type, boolean isReturn) { + + String methodId = ProtoBufHelper.getFullName(msg.getClass()); + + recordCount(methodId); + recordMessage(methodId, msg, dest); + + RequestMessageCallback callback = requestMessageCallback.get(methodId); + + if (callback != null) { + return callback.sendRequest(msg, dest, type, isReturn); + } + + if (isReturn) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new Exception("none callback")); + return future; + } + + return null; + } + + private void recordCount(String methodId){ + synchronized (requestCount) { + + Integer count = requestCount.get(methodId); + if (count == null) { + requestCount.putIfAbsent(methodId, 1); + } else { + requestCount.put(methodId, count+1); + } + } + + } + private void recordMessage(String methodId, Message msg, List dest){ + synchronized (requestMessage) { + Map> peerMessage = requestMessage.get(methodId); + if (peerMessage == null) { + peerMessage = new HashMap<>(); + requestMessage.put(methodId, peerMessage); + } + + for (RoutableID routableID : dest) { + List listMessage = peerMessage.get(routableID); + if (listMessage == null) { + listMessage = new ArrayList<>(); + } + + listMessage.add(msg); + peerMessage.put(routableID, listMessage); + + + + } + } + } + + public void sendFrowardRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn, HeaderHelper header) { + String methodId = methodId(msg); + ForwardRequestMessageCallback callback = forwardRequestMessageCallbackHashMap.get(methodId); + + Asserts.notNull(callback, "no callback deal methodid:" + methodId); + if (callback != null) { + callback.sendRequest(msg, dest, type, isReturn, header); + } + + + } + + @Override + public void sendResponse(Message msg, String transactionID, List dest, MessageRouter.ForwardingOptionType type) { + String methodId = methodId(msg); + ResponseMessageCallback callback = responseMessageCallback.get(methodId); + + Asserts.notNull(callback, "no callback deal methodid:" + methodId); + if (callback != null) { + callback.sendResponse(msg, transactionID, dest, type); + } + + + } + + + + + + + + + + @Override + public void handleResponse(String id, Message msg) { + + } + @Override + public void handleException(String id, Throwable throwable) { + + } + + + public interface RequestMessageCallback { + CompletableFuture sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn); + } + + public interface ForwardRequestMessageCallback { + void sendRequest(Message msg, List dest, MessageRouter.ForwardingOptionType type, boolean isReturn, HeaderHelper header); + } + + public interface ResponseMessageCallback { + void sendResponse(Message msg, String transactionID, List dest, MessageRouter.ForwardingOptionType type); + } +} diff --git a/p2p/src/test/java/org/platon/p2p/test/proto/AnyTest.java b/p2p/src/test/java/org/platon/p2p/test/proto/AnyTest.java new file mode 100644 index 0000000..0a3a295 --- /dev/null +++ b/p2p/src/test/java/org/platon/p2p/test/proto/AnyTest.java @@ -0,0 +1,72 @@ +package org.platon.p2p.test.proto; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors; +import org.apache.commons.lang3.StringUtils; +import org.platon.p2p.proto.attach.AttachMessage; +import org.platon.p2p.proto.attach.AttachProtos; +import org.platon.p2p.proto.common.NodeID; +import org.platon.p2p.proto.common.RoutableID; +import org.platon.p2p.proto.platon.Body; +import org.platon.p2p.proto.platon.Header; +import org.platon.p2p.proto.platon.PlatonMessage; + + +public class AnyTest { + + public static void main(String[] args){ + AnyTest anyTest = new AnyTest(); + + try { + anyTest.testAny(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void testAny() throws Exception { + RoutableID destID = RoutableID.newBuilder().setId(ByteString.copyFromUtf8("1122")).setType(RoutableID.DestinationType.NODEIDTYPE).build(); + NodeID viaID = NodeID.newBuilder().setId(ByteString.copyFromUtf8("3344")).build(); + + Header header = Header.newBuilder().setTxId("txId").setTtl(10).addDest(destID).addVia(viaID).setMsgType("Ping").build(); + + AttachMessage attachMessage = AttachMessage.newBuilder().setNodeId(viaID).build(); + + Body message = Body.newBuilder().setData(Any.pack(attachMessage)).build(); + + PlatonMessage platonMessage = PlatonMessage.newBuilder().setHeader(header).setBody(message).build(); + + + byte[] msgBytes = platonMessage.toByteArray(); + + PlatonMessage platonMessage1 = PlatonMessage.parseFrom(msgBytes); + System.out.println("header-msgType:" + platonMessage1.getHeader().getMsgType()); + System.out.println("header-txId:" + platonMessage1.getHeader().getTxId()); + System.out.println("header-ttl:" + platonMessage1.getHeader().getTtl()); + platonMessage1.getHeader().getDestList().forEach(nodeID -> { + System.out.println("header-destNodeId:" + nodeID); + }); + + platonMessage1.getHeader().getViaList().forEach(nodeID -> { + System.out.println("header-viaNodeId:" + nodeID); + }); + + Any any = platonMessage1.getBody().getData(); + + String messageName = StringUtils.substringAfter(any.getTypeUrl(), "/"); + System.out.println("body-messageFullName:" + messageName); + + + + Class clz = Class.forName(messageName); + + Object test = platonMessage1.getBody().getData().unpack(clz); + System.out.println("test:" + test); + + + + + } + +} diff --git a/p2p/src/test/resources/config/node1/node.conf b/p2p/src/test/resources/config/node1/node.conf new file mode 100644 index 0000000..58886eb --- /dev/null +++ b/p2p/src/test/resources/config/node1/node.conf @@ -0,0 +1,54 @@ +node = { + ip=127.0.0.1 + port=11001 + public-key=0x044c73f76a65ada74e582fecfe576043657b61a71bdff3f21cf00e3a4f3a4a19212a7510efbe32c7d1f4d041b5cea3b1d0a5f3238af89f4f42b256b3d95702ec97 + private-key=0x74f77061f293128b3165cb5bdbe829b0b80befebb6659c224f08a4d26a04ad0f + #in seconds + create.session.timeout = 1011 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 1211 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 3 + + active.list = + [ + { + ip=127.0.0.1 + port=11000 + public-key=0x0402720f70c4ab72ad781fbc77be02e30f1e67e98ac9bd2caeb26105e4c2ff637861feae41e783e792693da2d7adae039e058de525c8114cce3de76297dd23610e + } + ] +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} diff --git a/p2p/src/test/resources/config/node2/node.conf b/p2p/src/test/resources/config/node2/node.conf new file mode 100644 index 0000000..9e58a30 --- /dev/null +++ b/p2p/src/test/resources/config/node2/node.conf @@ -0,0 +1,53 @@ +node = { + ip=127.0.0.1 + port=11002 + public-key=0x04a9f61f6a5e7bfbe567cba25133f6aced2740a90c0959075b87bbc4fc12889824ab48ab2d6cfe4c541e9eadef1b66741e7a97811f6fddced4f3b32e3e101c255f + private-key=0x27306998a17f0779b10f0785d8432591c1ae6a15132a2309a22fbbee1417afd4 + #in seconds + create.session.timeout = 1011 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 1211 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 3 + active.list = + [ + { + ip=127.0.0.1 + port=11000 + public-key=0x0402720f70c4ab72ad781fbc77be02e30f1e67e98ac9bd2caeb26105e4c2ff637861feae41e783e792693da2d7adae039e058de525c8114cce3de76297dd23610e + } + ] +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} diff --git a/p2p/src/test/resources/config/seed/node.conf b/p2p/src/test/resources/config/seed/node.conf new file mode 100644 index 0000000..25a4e30 --- /dev/null +++ b/p2p/src/test/resources/config/seed/node.conf @@ -0,0 +1,48 @@ +node = { + ip=127.0.0.1 + port=11000 + public-key=0x0402720f70c4ab72ad781fbc77be02e30f1e67e98ac9bd2caeb26105e4c2ff637861feae41e783e792693da2d7adae039e058de525c8114cce3de76297dd23610e + private-key=0x53a200788bb50856b89b0e26366d811445cb0817177d4c2bcb8cf5d1a482cb09 + #in seconds + create.session.timeout = 1011 + + #in seconds, message.response.timeout > create.session.timeout + message.response.timeout = 1211 + #in seconds + peer.connect.timeout = 2 + + #in seconds + time.interval.for.duplicated.message = 3 + active.list = + [ + ] +} + +kad.plugin = { + id-length = 20 + restore-interval = 50000 + response-timeout = 1 + operation-timeout = 0 + max-concurrent-messages-transiting = 1 + K-value = 8 + replacement-cache-size = 8 + stale-times = 8 +} + +lmdb = { + lmdbjava-native-lib = "lmdb\\liblmdb.dll" + lmdb-data-file = "data\\platon.db" + lmdb-name = platon_p2p + lmdb-max-readers = 30 +} + +redir = { + hashrate = { + branching-factor = 10 + level = 3 + lowest-key = 1 + highest-key = 100 + start-level = 2s + algorithm = hashrate + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..fad10b7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,13 @@ +rootProject.name = 'platon' +include 'slice' +include 'account' +include 'vm' +include 'storage' +include 'crypto' +include 'consensus' +include 'core' +include 'common' +include 'p2p' +include 'trie' +include 'tests' + diff --git a/slice/build.gradle b/slice/build.gradle new file mode 100644 index 0000000..b64bb46 --- /dev/null +++ b/slice/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'java' +apply plugin: "com.google.protobuf" +apply plugin: 'idea' + +def grpcVersion = '1.15.0' +def protoVersion = '3.6.1' + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protoVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc { + outputSubDir = "java" + } + } + } + + generatedFilesBaseDir = "$projectDir/src" +} + +sourceSets { + main { + proto { + srcDir 'src/main/proto' + include '**/*.proto' + } + } +} + + +dependencies { + compile( + "com.google.protobuf:protobuf-java:${protoVersion}", + ) + compile group: 'io.netty', name: 'netty-all', version: '4.1.28.Final' + + compile "com.google.api.grpc:proto-google-common-protos:1.12.0" + compile "io.grpc:grpc-alts:${grpcVersion}" + compile "io.grpc:grpc-netty:${grpcVersion}" + compile "io.grpc:grpc-protobuf:${grpcVersion}" + compile "io.grpc:grpc-stub:${grpcVersion}" + + testCompile "io.grpc:grpc-testing:${grpcVersion}" +} + + +buildscript { + repositories { + maven { + url "http://maven.aliyun.com/nexus/content/groups/public/" + } + } + dependencies { + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6' + } +} diff --git a/slice/src/main/proto/org/platon/core/account.proto b/slice/src/main/proto/org/platon/core/account.proto new file mode 100644 index 0000000..bf55eb1 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/account.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package platon.account; + +option java_package = "org.platon.core.proto"; +option java_outer_classname = "AccountMessage"; +option java_string_check_utf8 = true; +option java_multiple_files = true; + +message AccountProto { + bytes balance = 1; + bytes extraRoot = 2; + bytes binHash = 3; + bytes permissionRoot = 4; +} + +message PermissionAddrProto { + BytesList addressList = 1; + BytesList urlList = 2; +} +message BytesList { + repeated bytes ele=1; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/base.proto b/slice/src/main/proto/org/platon/core/base.proto new file mode 100644 index 0000000..376b07c --- /dev/null +++ b/slice/src/main/proto/org/platon/core/base.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +option java_package = "org.platon.common.proto"; +option java_outer_classname = "BaseProto"; + +message BaseInt { + int32 intData = 1; +} + +message BaseLong { + int64 longData = 1; +} + +message BaseString { + string stringData = 1; +} + +message BaseBytesList { + repeated bytes bytesListData = 1; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/block.proto b/slice/src/main/proto/org/platon/core/block.proto new file mode 100644 index 0000000..f527829 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/block.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +option java_package = "org.platon.core.block.proto"; +option java_outer_classname = "BlockProto"; + +message Block { + bytes headerbytes = 1; + repeated bytes transactionbytes = 2; + repeated bytes unclesbytes = 3; + repeated bytes signatures = 4; +} + +message BlockInfo { + int64 number = 1; + bytes totalDifficulty = 2; + bytes parentHash = 3; + repeated bytes children = 4; + repeated bytes bloomlog = 5; +} + +message TransactionPosition { + bytes blockHash = 1; + int32 index = 2; +} +message BlockReceipts { + repeated bytes txreceipts = 1; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/blockheader.proto b/slice/src/main/proto/org/platon/core/blockheader.proto new file mode 100644 index 0000000..5bcde7b --- /dev/null +++ b/slice/src/main/proto/org/platon/core/blockheader.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + + + +option java_package = "org.platon.core.block.proto"; +option java_outer_classname = "BlockHeaderProto"; + + +message BlockHeader { + int64 timestamp = 1; + int64 number = 2; + bytes parentHash = 3; + bytes stateRoot = 4; + bytes permissionRoot = 5; + bytes dposRoot = 6; + bytes transactionRoot = 7; + bytes transferRoot = 8; + bytes votingRoot = 9; + bytes receiptRoot = 10; + bytes bloomLog = 11; + bytes energonUsed = 12; + bytes energonCeiling = 13; + bytes difficulty = 14; + bytes extraData = 15; + bytes author = 16; + bytes coinbase = 17; +} diff --git a/slice/src/main/proto/org/platon/core/dataword.proto b/slice/src/main/proto/org/platon/core/dataword.proto new file mode 100644 index 0000000..e28155d --- /dev/null +++ b/slice/src/main/proto/org/platon/core/dataword.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package platon.block; + + +option java_package = "org.platon.core.proto"; +option java_outer_classname = "DatawordProto"; +option java_multiple_files = true; + +message DataWordMessage { + bytes data = 1; +} + +message EmptyBytesMessage { + bytes data = 1; +} + +message IntMessage { + int32 data = 1; +} + + +message BlockIdentifierMessage { + bytes hash = 1; + int64 number = 2; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/indexBlockInfo.proto b/slice/src/main/proto/org/platon/core/indexBlockInfo.proto new file mode 100644 index 0000000..2427ee0 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/indexBlockInfo.proto @@ -0,0 +1,19 @@ + + +syntax = "proto3"; + +package platon.block; + +option java_package = "org.platon.core.proto"; +option java_outer_classname = "BlockInfoProto"; +option java_multiple_files = true; + +message BlockInfo { + bytes hash = 1; + bool mainChain = 2; + bytes totalDifficulty = 3; +} + +message BlockInfoList { + repeated BlockInfo blockInfoList = 1; +} diff --git a/slice/src/main/proto/org/platon/core/message/errno.proto b/slice/src/main/proto/org/platon/core/message/errno.proto new file mode 100644 index 0000000..fa3745f --- /dev/null +++ b/slice/src/main/proto/org/platon/core/message/errno.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package platon.message; + +option java_package = "org.platon.slice.message"; +option java_outer_classname = "ResultCodeMessage"; +option java_string_check_utf8 = true; + +enum ResultCode { + SUCCESS = 0; + FAIL = 1; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/message/request.proto b/slice/src/main/proto/org/platon/core/message/request.proto new file mode 100644 index 0000000..13940e8 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/message/request.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package platon.message; + + +import "org/platon/core/transaction.v2.proto"; + +option java_package = "org.platon.slice.message.request"; +option java_outer_classname = "TransactionRequestProto"; +option java_string_check_utf8 = true; +option java_multiple_files = true; + +message TransactionBaseRequest { + platon.tx.TransactionBody body = 1; + string signature = 2; + string from = 3; +} + +message GetTransactionReceiptByHashRequest{ + string txHash = 1; +} + +message VoidRequest { + +} + +message GetTransactionCountRequest{ + string address = 1; + string blockParams = 2; +} + +message GetBalanceRequest{ + string address = 1; + string blockParams = 2; +} + +message GetBlockTransactionCountByHashRequest { + string blockHash = 1; +} + +message GetBlockTransactionCountByNumberRequest { + string blockNumber = 1; +} + +message GetBlockByHashRequest { + string blockHash = 1; + bool returnFull = 2; +} + +message GetBlockByNumberRequest{ + string blockNumber = 1; + bool returnFull = 2; +} + +message GetTransactionByHashRequest { + string txHash = 1; +} + +message GetTransactionByBlockHashAndIndexRequest { + string blockHash = 1; + sint64 index = 2; +} + +message GetTransactionByBlockNumberAndIndexRequest { + string blockNumber = 1; + sint64 index = 2; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/message/response.proto b/slice/src/main/proto/org/platon/core/message/response.proto new file mode 100644 index 0000000..ec1273a --- /dev/null +++ b/slice/src/main/proto/org/platon/core/message/response.proto @@ -0,0 +1,100 @@ +syntax = "proto3"; + +package platon.message; + +option java_package = "org.platon.slice.message.response"; +option java_outer_classname = "ResponseMessage"; +option java_string_check_utf8 = true; +option java_multiple_files = true; + +import "google/protobuf/any.proto"; +import "org/platon/core/message/errno.proto"; + +message BaseResponse { + platon.message.ResultCode code = 1; + string msg = 2; + google.protobuf.Any data= 10; +} + +message TransactionResponse { + string hash = 1; + string blockHash = 2; + int64 blockNumber = 3; + int32 transactionIndex = 4; + string from = 5; + string to = 6; + bytes value = 7; + bytes energonPrice = 8; + bytes energonLimit = 9; + string input = 10; + +} + +message TransactionReceiptResponse { + string transactionhash = 1; + int32 transactionIndex = 2; + string blockHash = 3; + int64 blockNumber = 4; + string from = 5; + string to = 6; + bytes cumulativeEnergonUsed = 7; + bytes energonUsed = 8; + string contractAddr = 9; + repeated LogEntry logs = 10; +} + +message LogEntry { + string blockHash = 1; + string address = 2; + string logIndex = 3; + string data = 4; + repeated string topics = 5; + int64 blockNumber = 6; + int32 transactionIndex = 7; + string transactionHash = 8; +} + +message BlockResponse { + int64 blockNumber = 1; + string hash = 2; + string parentHash = 3; + string logsBloom = 5; + string transactionsRoot = 6; + string stateRoot = 7; + string receiptsRoot = 8; + string miner = 9; + int64 difficulty = 10; + int64 totalDifficulty = 11; + string extraData = 12; + int64 size = 13; + bytes energonLimit = 14; + bytes energonUsed = 15; + int64 timestamp = 16; + repeated string transactions= 17; + + reserved 20 to 30 ; + reserved "uncles","nonce","sha3Uncles"; + +} + +message SyncingResultResponse { + string startingBlock = 1; + string currentBlock = 2; + string highestBlock = 3; +} + +message StringResponse { + string data = 1; +} + +message IntResponse { + int64 data = 1; +} + +message BoolResponse { + bool data = 1; +} + +message StringArrayResponse { + repeated string data = 1; +} diff --git a/slice/src/main/proto/org/platon/core/service/platon.gprc.proto b/slice/src/main/proto/org/platon/core/service/platon.gprc.proto new file mode 100644 index 0000000..088867c --- /dev/null +++ b/slice/src/main/proto/org/platon/core/service/platon.gprc.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +import "org/platon/core/message/request.proto"; +import "org/platon/core/message/response.proto"; + +package platon.service.grpc; + +option java_package = "org.platon.service.grpc"; +option java_multiple_files = true; +option java_outer_classname = "PlatonGrpcService"; +option objc_class_prefix = "ATP"; + +service PlatonService { + + rpc SayHello(MessageRequest) returns (MessageResponse) {} + + rpc SayHelloWithReqStream(stream MessageRequest) returns (MessageResponse){} + + rpc SayHelloWithRespStream(MessageRequest) returns (stream MessageResponse){} + + rpc SayHelloWithBothStream(stream MessageRequest) returns (stream MessageResponse){} + + + rpc atpCall(platon.message.TransactionBaseRequest) returns(platon.message.BaseResponse){} + + rpc atpSendTransaction(platon.message.TransactionBaseRequest ) returns(platon.message.BaseResponse){} + + rpc atpGetTransactionReceiptByHash(platon.message.GetTransactionReceiptByHashRequest) returns(platon.message.BaseResponse){} + + rpc atpSendFillTransaction(platon.message.TransactionBaseRequest ) returns(platon.message.BaseResponse){} + + rpc atpEnergonPrice(platon.message.VoidRequest) returns (platon.message.BaseResponse){} + + rpc atpGetTransactionCount(platon.message.GetTransactionCountRequest) returns(platon.message.BaseResponse){} + + rpc atpProtocolVersion(platon.message.VoidRequest) returns(platon.message.BaseResponse){} + + rpc atpCoinbase(platon.message.VoidRequest) returns(platon.message.BaseResponse){} + + rpc atpAccounts(platon.message.VoidRequest) returns(platon.message.BaseResponse){} + + rpc atpBlockNumber(platon.message.VoidRequest) returns(platon.message.BaseResponse){} + + rpc atpGetBalance(platon.message.GetBalanceRequest) returns(platon.message.BaseResponse){} + + rpc atpGetBlockTransactionCountByHash(platon.message.GetBlockTransactionCountByHashRequest) returns(platon.message.BaseResponse){} + + rpc atpGetBlockTransactionCountByNumber(platon.message.GetBlockTransactionCountByNumberRequest) returns(platon.message.BaseResponse){} + + rpc atpGetBlockByHash(platon.message.GetBlockByHashRequest) returns(platon.message.BaseResponse){} + + rpc atpGetBlockByNumber(platon.message.GetBlockByNumberRequest) returns(platon.message.BaseResponse){} + + rpc atpGetTransactionByHash(platon.message.GetTransactionByHashRequest) returns(platon.message.BaseResponse){} + + rpc atpGetTransactionByBlockHashAndIndex(platon.message.GetTransactionByBlockHashAndIndexRequest) returns(platon.message.BaseResponse){} + + rpc atpGetTransactionByBlockNumberAndIndex(platon.message.GetTransactionByBlockNumberAndIndexRequest) returns(platon.message.BaseResponse){} + +} + + +message MessageRequest { + string name = 1; +} + + +message MessageResponse { + sint64 age = 1; +} diff --git a/slice/src/main/proto/org/platon/core/transaction.v2.proto b/slice/src/main/proto/org/platon/core/transaction.v2.proto new file mode 100644 index 0000000..e7eb4ee --- /dev/null +++ b/slice/src/main/proto/org/platon/core/transaction.v2.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package platon.tx; + +import "google/protobuf/any.proto"; + +option java_package = "org.platon.core.transaction.proto"; +option java_outer_classname = "TransactionProto"; +option java_multiple_files = true; + +enum TransactionType { + TRANSACTION = 0; + VOTE = 1; + CONTRACT_DEPLOY = 2; + CONTRACT_CALL = 3; + TRANSACTION_MPC = 4; + QUERY_CALL = 5; + PERMISSION_UPDATE = 6; +} + +message TransactionBody { + TransactionType type = 1; + bytes value = 2; + bytes receiveAddress = 3; + + int64 referenceBlockNum = 6; + bytes referenceBlockHash = 7; + bytes energonPrice = 8; + bytes energonLimit = 9; + google.protobuf.Any data = 10; +} + +message Transaction { + TransactionBody body = 1; + bytes signature = 2; +} + + +message ContractDeployRequest { + string className = 1; + bytes data = 2; +} + +message ContractRequest { + string operation = 1; + string className = 2; + bytes data = 3; +} + +message MPCTransactionRequest { + string operation = 1; + string className = 2; + bytes data = 3; + repeated string partners= 4; + repeated string results = 5; + bytes inData = 6; + map nodeIdAddrMap= 7; + string algorithmId = 8; + map extraArgs = 9; + string mpcContractAddr = 10; +} + + +message defaultRequestData { + bytes data = 1; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/core/transactionInfo.proto b/slice/src/main/proto/org/platon/core/transactionInfo.proto new file mode 100644 index 0000000..5ca33a4 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/transactionInfo.proto @@ -0,0 +1,22 @@ + + +syntax = "proto3"; + +package platon.block; + +import "org/platon/core/transactionReceipt.v2.proto"; + +option java_package = "org.platon.core.proto"; +option java_outer_classname = "TransactionInfoProto"; +option java_multiple_files = true; + +message TransactionInfoMessage { + platon.tx.TransactionReceiptBase receipt = 1; + bytes blockHash = 2; + bytes parentBlockHash = 3; + int64 index = 4; +} + +message TransactionInfoMessageList { + repeated TransactionInfoMessage txInfoList = 1; +} diff --git a/slice/src/main/proto/org/platon/core/transactionReceipt.v2.proto b/slice/src/main/proto/org/platon/core/transactionReceipt.v2.proto new file mode 100644 index 0000000..397ebf2 --- /dev/null +++ b/slice/src/main/proto/org/platon/core/transactionReceipt.v2.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + + + +package platon.tx; + +option java_package = "org.platon.core.transaction.proto"; +option java_outer_classname = "TransactionReceiptProto"; + +message LogEntry { + bytes address = 1; + repeated bytes topic = 2; + bytes data = 3; +} + +message TransactionReceiptBase { + bytes energonUsed = 1; + bytes cumulativeEnergon = 2; + bytes stateRoot = 3; + bytes bloomFilter = 4; + bytes executionResult = 5; + repeated LogEntry logs = 6; + + + + + + +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/p2p/attach/attach.proto b/slice/src/main/proto/org/platon/p2p/attach/attach.proto new file mode 100644 index 0000000..4157b73 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/attach/attach.proto @@ -0,0 +1,28 @@ + +syntax = "proto3"; + +package org.platon.p2p.proto.attach; + +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.attach"; +option java_outer_classname = "AttachProtos"; + +message AttachMessage { + org.platon.p2p.proto.common.NodeID nodeId = 1; +} + +message AttachRespMessage { + org.platon.p2p.proto.common.NodeID nodeId = 1; +} + +message PingMessage { + int32 payloadLength = 1; +} + +message PongMessage { + int64 responseId = 1; + int64 responseTime = 2; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/p2p/common/common.proto b/slice/src/main/proto/org/platon/p2p/common/common.proto new file mode 100644 index 0000000..5b00896 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/common/common.proto @@ -0,0 +1,42 @@ + +syntax = "proto3"; + +package org.platon.p2p.proto.common; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.common"; +option java_outer_classname = "CommonProtos"; + +message RoutableID { + enum DestinationType { + NODEIDTYPE = 0; + RESOURCEIDTYPE = 1; + OPAQUEIDTYPE = 2; + } + + DestinationType type = 1; + bytes id = 2; +} + +message NodeID { + bytes id = 1; + + + string endpoint = 2; + + string relay = 3; + + + bytes pubKey = 4; +} + +message ResourceID { + bytes id = 1; +} + +message OpaqueID { + bytes id = 1; +} + + diff --git a/slice/src/main/proto/org/platon/p2p/consensus/bftmessage.proto b/slice/src/main/proto/org/platon/p2p/consensus/bftmessage.proto new file mode 100644 index 0000000..227606d --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/consensus/bftmessage.proto @@ -0,0 +1,16 @@ + +syntax = "proto3"; +package org.platon.p2p.proto.consensus; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.consensus"; +option java_outer_classname = "Bftprotos"; + + +message PrepareAckMessage { + bytes blockHash = 1; + bytes signature = 2; +} + + diff --git a/slice/src/main/proto/org/platon/p2p/platonmessage.proto b/slice/src/main/proto/org/platon/p2p/platonmessage.proto new file mode 100644 index 0000000..a6994f9 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/platonmessage.proto @@ -0,0 +1,31 @@ + +syntax = "proto3"; +package org.platon.p2p.proto.platon; +import "google/protobuf/any.proto"; +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.platon"; +option java_outer_classname = "PlatonProtos"; + +message Header { + string version = 1; + string txId = 2; + repeated org.platon.p2p.proto.common.NodeID via = 3; + repeated org.platon.p2p.proto.common.RoutableID dest = 4; + int32 ttl = 5; + string msgType = 6; + +} + +message Body { + google.protobuf.Any data = 1; +} + +message PlatonMessage { + Header header = 1; + Body body = 2; +} + + diff --git a/slice/src/main/proto/org/platon/p2p/plugin/plugin.proto b/slice/src/main/proto/org/platon/p2p/plugin/plugin.proto new file mode 100644 index 0000000..fd7e3b9 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/plugin/plugin.proto @@ -0,0 +1,28 @@ + +syntax = "proto3"; +package org.platon.p2p.proto.plugin; + +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.plugin"; +option java_outer_classname = "PluginProtos"; + +message JoinMessage { + org.platon.p2p.proto.common.NodeID nodeId = 1; +} + +message JoinRespMessage { + repeated org.platon.p2p.proto.common.NodeID nodeId = 1; +} + +message QueryMessage { + org.platon.p2p.proto.common.RoutableID routableId = 1; +} + + +message QueryRespMessage { + repeated org.platon.p2p.proto.common.NodeID nodeId = 1; +} + diff --git a/slice/src/main/proto/org/platon/p2p/pubsub/pubsub.proto b/slice/src/main/proto/org/platon/p2p/pubsub/pubsub.proto new file mode 100644 index 0000000..240bd3f --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/pubsub/pubsub.proto @@ -0,0 +1,83 @@ + +syntax = "proto3"; +package org.platon.p2p.proto.pubsub; +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.pubsub"; +option java_outer_classname = "PubsubProtos"; + +message SubMessage { + + string topic = 1; + + + bool sub = 2; + + + bytes nodeId = 3; +} + +message EntryMessage { + + bytes fromNodeId = 1; + + + string key = 2; + + + string topic = 3; + + + bytes data = 4; +} + +message IHaveMessage { + + string topic = 1; + + + repeated string messageId = 2; +} + + +message IWantMessage { + + string topic = 1; + + + repeated string messageId = 2; +} + +message GraftMessage { + + repeated string topic = 1; + + + bytes nodeId = 2; +} + + +message PruneMessage { + + bytes nodeId = 1; + + + repeated string topic = 2; +} + + +message ControlMessage { + repeated IHaveMessage ihave = 1; + repeated IWantMessage iwant = 2; + GraftMessage graft = 3; + PruneMessage prune = 4; +} + +message TopicMessage { + bytes fromNodeId = 1; + SubMessage subscribe = 2; + repeated EntryMessage publishedEntry = 3; + ControlMessage control = 4; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/p2p/redir/redir.proto b/slice/src/main/proto/org/platon/p2p/redir/redir.proto new file mode 100644 index 0000000..4bf30c2 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/redir/redir.proto @@ -0,0 +1,40 @@ + +syntax = "proto3"; + +package org.platon.p2p.proto.redir; +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.redir"; +option java_outer_classname = "RedirProtos"; + + + +message ServiceEntry { + string serviceType = 1; + string describe = 2; + string sourceKey = 3; + string from = 4; + string url = 5; +} + +message ReDiRMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; + ServiceEntry entry = 2; +} +message ReDiRRespMessage { + repeated string key = 1; +} + +message ReDiRFindMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; + string findKey = 2; +} + +message ReDiRFindRespMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; + repeated ServiceEntry entry = 2; + repeated string key = 3; +} + diff --git a/slice/src/main/proto/org/platon/p2p/session/session.proto b/slice/src/main/proto/org/platon/p2p/session/session.proto new file mode 100644 index 0000000..9660f52 --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/session/session.proto @@ -0,0 +1,28 @@ + +syntax = "proto3"; +package org.platon.p2p.proto.session; +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.session"; +option java_outer_classname = "SessionProtos"; + +message CreateSession { + bytes clientNodeId = 1; + + + string endpoint = 2; + + + bytes messageHash = 3; + + + bytes signature = 4; +} + +message SayHello { + bytes nodeId = 1; + string hello = 2; + bool feedback = 3; +} \ No newline at end of file diff --git a/slice/src/main/proto/org/platon/p2p/storage/storage.proto b/slice/src/main/proto/org/platon/p2p/storage/storage.proto new file mode 100644 index 0000000..7a3e5ed --- /dev/null +++ b/slice/src/main/proto/org/platon/p2p/storage/storage.proto @@ -0,0 +1,129 @@ + +syntax = "proto3"; + +package org.platon.p2p.proto.storage; +import "org/platon/p2p/common/common.proto"; + + +option java_multiple_files = true; +option java_package = "org.platon.p2p.proto.storage"; +option java_outer_classname = "StorageProtos"; + +enum StoreStatus { + SUCCESS = 0; + FAILED = 1; +}; + +message StoredData { + int64 storageTime = 1; + int64 lifeTime = 2; + bytes key = 3; + bytes value = 4; + bytes signature = 5; +} + +message StoreDataEntry { + int32 replica = 1; + repeated org.platon.p2p.proto.common.RoutableID replicaNodeId = 2; + org.platon.p2p.proto.common.ResourceID resourceId = 3; + StoredData storedData = 4; +} + + +message SetStoreMessage { + int32 replica = 1; + repeated org.platon.p2p.proto.common.RoutableID replicaNodeId = 2; + org.platon.p2p.proto.common.ResourceID resourceId = 3; + StoredData storedData = 4; +} + +message SetStoreRespMessage { + StoreStatus status = 1; + string error = 2; +} + + +message GetStoreMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; +} + +message GetStoreRespMessage { + StoredData storedData = 1; +} + + + +message DelStoreMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; +} + +message DelStoreRespMessage { + StoreStatus status = 1; + string error = 2; +} + + +message HashStoreDataEntry { + int32 replica = 1; + repeated org.platon.p2p.proto.common.RoutableID replicaNodeId = 2; + org.platon.p2p.proto.common.ResourceID resourceId = 3; + StoredData storedData = 4; + bytes key = 5; +} + + +message HSetStoreMessage { + int32 replica = 1; + repeated org.platon.p2p.proto.common.RoutableID replicaNodeId = 2; + org.platon.p2p.proto.common.ResourceID resourceId = 3; + StoredData storedData = 4; + bytes key = 5; +} + +message HSetStoreRespMessage { + StoreStatus status = 1; + string error = 2; +} + + + + +message HGetStoreMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; + bytes key = 2; +} + +message HGetStoreRespMessage { + repeated StoredData storedData = 1; +} + + +message HGetAllStoreMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; +} + +message HGetAllStoreRespMessage { + repeated StoredData storedData = 1; +} + + + + +message HDelStoreMessage { + org.platon.p2p.proto.common.ResourceID resourceId = 1; + bytes key = 2; +} + +message HDelStoreRespMessage { + StoreStatus status = 1; + string error = 2; +} + + + + + + + + + diff --git a/slice/src/test/java/org/platon/slice/grpc/StreamObserverManager.java b/slice/src/test/java/org/platon/slice/grpc/StreamObserverManager.java new file mode 100644 index 0000000..1daf17b --- /dev/null +++ b/slice/src/test/java/org/platon/slice/grpc/StreamObserverManager.java @@ -0,0 +1,70 @@ +package org.platon.slice.grpc; + +import io.grpc.stub.StreamObserver; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + + +public class StreamObserverManager implements StreamObserver { + + private ReentrantLock lock = new ReentrantLock(); + private Condition condition = lock.newCondition(); + private T response ; + private boolean endFlag = false; + private Throwable e; + + @Override + public void onNext(T value) { + this.response = value; + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + this.e = t; + endFlag = true; + signal(); + } + + @Override + public void onCompleted() { + endFlag = true; + signal(); + } + + public void signal(){ + try { + lock.lock(); + + condition.signal(); + + }catch (Exception e) { + e.printStackTrace(); + }finally { + lock.unlock(); + } + } + + public T getResponse() throws Exception { + lock.lock(); + try { + if(!endFlag){ + + condition.await(); + + } + if (e != null) { + throw new Exception(e); + } + if (null != this.response) { + return this.response; + } + }catch (Exception e){ + throw e; + }finally { + lock.unlock(); + } + return null; + } +} diff --git a/storage/build.gradle b/storage/build.gradle new file mode 100644 index 0000000..6ebd5ec --- /dev/null +++ b/storage/build.gradle @@ -0,0 +1,21 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile project(":slice") + compile project(":crypto") + compile "org.iq80.leveldb:leveldb:0.10" //LevelDB + compile "org.rocksdb:rocksdbjni:5.11.3" //RocksDB + compile "com.google.protobuf:protobuf-java:3.5.1" + compile "org.bouncycastle:bcprov-jdk15on:1.59" + testCompile "org.springframework:spring-test:${springVersion}" + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +task createJavaProject << { + sourceSets*.java.srcDirs*.each{ it.mkdirs() } + sourceSets*.resources.srcDirs*.each{ it.mkdirs()} +} \ No newline at end of file diff --git a/storage/src/main/java/org.platon.storage/datasource/AbstractCachedSource.java b/storage/src/main/java/org.platon.storage/datasource/AbstractCachedSource.java new file mode 100644 index 0000000..3d89e65 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/AbstractCachedSource.java @@ -0,0 +1,79 @@ +package org.platon.storage.datasource; + +public abstract class AbstractCachedSource + extends AbstractChainedSource + implements CachedSource { + + private final Object lock = new Object(); + + + public interface Entry { + V value(); + } + + static final class SimpleEntry implements Entry { + private V val; + + public SimpleEntry(V val) { + this.val = val; + } + + public V value() { + return val; + } + } + + protected MemSizeEstimator keySizeEstimator; + protected MemSizeEstimator valueSizeEstimator; + private int size = 0; + + public AbstractCachedSource(Source source) { + super(source); + } + + + abstract Entry getCached(Key key); + + + protected void cacheAdded(Key key, Value value) { + synchronized (lock) { + if (keySizeEstimator != null) { + size += keySizeEstimator.estimateSize(key); + } + if (valueSizeEstimator != null) { + size += valueSizeEstimator.estimateSize(value); + } + } + } + + + protected void cacheRemoved(Key key, Value value) { + synchronized (lock) { + if (keySizeEstimator != null) { + size -= keySizeEstimator.estimateSize(key); + } + if (valueSizeEstimator != null) { + size -= valueSizeEstimator.estimateSize(value); + } + } + } + + + protected void cacheCleared() { + synchronized (lock) { + size = 0; + } + } + + + public AbstractCachedSource withSizeEstimators(MemSizeEstimator keySizeEstimator, MemSizeEstimator valueSizeEstimator) { + this.keySizeEstimator = keySizeEstimator; + this.valueSizeEstimator = valueSizeEstimator; + return this; + } + + @Override + public long estimateCacheSize() { + return size; + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/AbstractChainedSource.java b/storage/src/main/java/org.platon.storage/datasource/AbstractChainedSource.java new file mode 100644 index 0000000..a25f33f --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/AbstractChainedSource.java @@ -0,0 +1,41 @@ +package org.platon.storage.datasource; + +public abstract class AbstractChainedSource implements Source { + + private Source source; + protected boolean flushSource; + + + protected AbstractChainedSource() { + } + + public AbstractChainedSource(Source source) { + this.source = source; + } + + + protected void setSource(Source src) { + source = src; + } + + public Source getSource() { + return source; + } + + public void setFlushSource(boolean flushSource) { + this.flushSource = flushSource; + } + + + @Override + public synchronized boolean flush() { + boolean ret = flushImpl(); + if (flushSource) { + ret |= getSource().flush(); + } + return ret; + } + + + protected abstract boolean flushImpl(); +} diff --git a/storage/src/main/java/org.platon.storage/datasource/AsyncFlushable.java b/storage/src/main/java/org.platon.storage/datasource/AsyncFlushable.java new file mode 100644 index 0000000..770063e --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/AsyncFlushable.java @@ -0,0 +1,12 @@ +package org.platon.storage.datasource; + +import com.google.common.util.concurrent.ListenableFuture; + +public interface AsyncFlushable { + + + void flipStorage() throws InterruptedException; + + + ListenableFuture flushAsync() throws InterruptedException; +} diff --git a/storage/src/main/java/org.platon.storage/datasource/AsyncWriteCache.java b/storage/src/main/java/org.platon.storage/datasource/AsyncWriteCache.java new file mode 100644 index 0000000..119dc5d --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/AsyncWriteCache.java @@ -0,0 +1,145 @@ +package org.platon.storage.datasource; + +import com.google.common.util.concurrent.*; +import org.platon.common.AppenderName; +import org.platon.storage.utils.AutoLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public abstract class AsyncWriteCache extends AbstractCachedSource + implements AsyncFlushable { + + private static final Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_DB); + + private static ListeningExecutorService flushExecutor = MoreExecutors.listeningDecorator( + Executors.newFixedThreadPool(2, new ThreadFactoryBuilder().setNameFormat("AsyncWriteCacheThread - %d").build())); + + protected volatile WriteCache curCache; + protected WriteCache flushingCache; + + private ListenableFuture lastFlush = Futures.immediateFuture(false); + + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + private final AutoLock rLock = new AutoLock(rwLock.readLock()); + private final AutoLock wLock = new AutoLock(rwLock.writeLock()); + + private String name = ""; + + public AsyncWriteCache(Source source) { + super(source); + flushingCache = createCache(source); + flushingCache.setFlushSource(true); + curCache = createCache(flushingCache); + } + + protected abstract WriteCache createCache(Source source); + + @Override + public Collection getModified() { + try (AutoLock l = rLock.lock()) { + return curCache.getModified(); + } + } + + @Override + public boolean hasModified() { + try (AutoLock l = rLock.lock()) { + return curCache.hasModified(); + } + } + + @Override + public void put(Key key, Value val) { + try (AutoLock l = rLock.lock()) { + curCache.put(key, val); + } + } + + @Override + public void delete(Key key) { + try (AutoLock l = rLock.lock()) { + curCache.delete(key); + } + } + + @Override + public Value get(Key key) { + try (AutoLock l = rLock.lock()) { + return curCache.get(key); + } + } + + @Override + public synchronized boolean flush() { + try { + flipStorage(); + flushAsync(); + return flushingCache.hasModified(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + Entry getCached(Key key) { + return curCache.getCached(key); + } + + @Override + public synchronized void flipStorage() throws InterruptedException { + + try { + if (!lastFlush.isDone()) + logger.debug("AsyncWriteCache (" + name + "): waiting for previous flush to complete"); + lastFlush.get(); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + + try (AutoLock l = wLock.lock()) { + flushingCache.cache = curCache.cache; + curCache = createCache(flushingCache); + } + } + + public synchronized ListenableFuture flushAsync() throws InterruptedException { + if (logger.isDebugEnabled()) { + logger.debug("AsyncWriteCache (" + name + "): flush submitted"); + } + lastFlush = flushExecutor.submit(() -> { + if (logger.isDebugEnabled()) { + logger.debug("AsyncWriteCache (" + name + "): flush started"); + } + long s = System.currentTimeMillis(); + boolean ret = flushingCache.flush(); + if (logger.isDebugEnabled()) { + logger.debug("AsyncWriteCache (" + name + "): flush completed in " + (System.currentTimeMillis() - s) + " ms"); + } + return ret; + }); + return lastFlush; + } + + @Override + public long estimateCacheSize() { + + + return (long) (curCache.estimateCacheSize() * 2.0); + } + + @Override + protected synchronized boolean flushImpl() { + return false; + } + + public AsyncWriteCache withName(String name) { + this.name = name; + return this; + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/BatchSource.java b/storage/src/main/java/org.platon.storage/datasource/BatchSource.java new file mode 100644 index 0000000..4069cdf --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/BatchSource.java @@ -0,0 +1,8 @@ +package org.platon.storage.datasource; + +import java.util.Map; + +public interface BatchSource extends Source { + + void updateBatch(Map rows); +} diff --git a/storage/src/main/java/org.platon.storage/datasource/BatchSourceWriter.java b/storage/src/main/java/org.platon.storage/datasource/BatchSourceWriter.java new file mode 100644 index 0000000..90aa284 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/BatchSourceWriter.java @@ -0,0 +1,43 @@ +package org.platon.storage.datasource; + +import java.util.HashMap; +import java.util.Map; + +public class BatchSourceWriter extends AbstractChainedSource { + + private Map buffer = new HashMap<>(); + + public BatchSourceWriter(BatchSource src) { + super(src); + } + + private BatchSource getBatchSource() { + return (BatchSource) getSource(); + } + + @Override + public synchronized void delete(Key key) { + buffer.put(key, null); + } + + @Override + public synchronized void put(Key key, Value val) { + buffer.put(key, val); + } + + @Override + public Value get(Key key) { + return getSource().get(key); + } + + @Override + public synchronized boolean flushImpl() { + if (!buffer.isEmpty()) { + getBatchSource().updateBatch(buffer); + buffer.clear(); + return true; + } else { + return false; + } + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/CachedSource.java b/storage/src/main/java/org.platon.storage/datasource/CachedSource.java new file mode 100644 index 0000000..0cf26f3 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/CachedSource.java @@ -0,0 +1,21 @@ +package org.platon.storage.datasource; + +import java.util.Collection; + +public interface CachedSource extends Source { + + + Source getSource(); + + + Collection getModified(); + + + boolean hasModified(); + + + long estimateCacheSize(); + + + interface BytesKey extends CachedSource {} +} diff --git a/storage/src/main/java/org.platon.storage/datasource/DbSettings.java b/storage/src/main/java/org.platon.storage/datasource/DbSettings.java new file mode 100644 index 0000000..524af1b --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/DbSettings.java @@ -0,0 +1,39 @@ +package org.platon.storage.datasource; + +public class DbSettings { + + public static final DbSettings DEFAULT = new DbSettings() + .withMaxThreads(1) + .withMaxOpenFiles(32); + + int maxOpenFiles; + int maxThreads; + + private DbSettings() { + } + + public static DbSettings newInstance() { + DbSettings settings = new DbSettings(); + settings.maxOpenFiles = DEFAULT.maxOpenFiles; + settings.maxThreads = DEFAULT.maxThreads; + return settings; + } + + public int getMaxOpenFiles() { + return maxOpenFiles; + } + + public DbSettings withMaxOpenFiles(int maxOpenFiles) { + this.maxOpenFiles = maxOpenFiles; + return this; + } + + public int getMaxThreads() { + return maxThreads; + } + + public DbSettings withMaxThreads(int maxThreads) { + this.maxThreads = maxThreads; + return this; + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/DbSource.java b/storage/src/main/java/org.platon.storage/datasource/DbSource.java new file mode 100644 index 0000000..874819d --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/DbSource.java @@ -0,0 +1,24 @@ +package org.platon.storage.datasource; + +import java.util.Set; + +public interface DbSource extends BatchSource { + + void setName(String name); + + void open() throws RuntimeException; + + void open(DbSettings settings) throws RuntimeException; + + boolean isAlive(); + + void reset(); + + void close() throws Exception; + + V prefixLookup(byte[] key, int prefixBytes) throws RuntimeException; + + String getName(); + + Set keys() throws RuntimeException; +} diff --git a/storage/src/main/java/org.platon.storage/datasource/HashedKeySource.java b/storage/src/main/java/org.platon.storage/datasource/HashedKeySource.java new file mode 100644 index 0000000..6651301 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/HashedKeySource.java @@ -0,0 +1,4 @@ +package org.platon.storage.datasource; + +public interface HashedKeySource extends Source { +} diff --git a/storage/src/main/java/org.platon.storage/datasource/MemSizeEstimator.java b/storage/src/main/java/org.platon.storage/datasource/MemSizeEstimator.java new file mode 100644 index 0000000..0a92346 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/MemSizeEstimator.java @@ -0,0 +1,12 @@ +package org.platon.storage.datasource; + +public interface MemSizeEstimator { + + long estimateSize(E e); + + MemSizeEstimator ByteArrayEstimator = bytes -> { + + return bytes == null ? 0 : bytes.length + 16; + }; + +} diff --git a/storage/src/main/java/org.platon.storage/datasource/MultiCache.java b/storage/src/main/java/org.platon.storage/datasource/MultiCache.java new file mode 100644 index 0000000..72fa1ff --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/MultiCache.java @@ -0,0 +1,51 @@ +package org.platon.storage.datasource; + +public abstract class MultiCache extends ReadWriteCache.BytesKey { + + public MultiCache(Source src) { + super(src, WriteCache.CacheType.SIMPLE); + } + + + @Override + public synchronized V get(byte[] key) { + AbstractCachedSource.Entry ownCacheEntry = getCached(key); + V ownCache = ownCacheEntry == null ? null : ownCacheEntry.value(); + if (ownCache == null) { + V v = getSource() != null ? super.get(key) : null; + ownCache = create(key, v); + put(key, ownCache); + } + return ownCache; + } + + + @Override + public synchronized boolean flushImpl() { + boolean ret = false; + for (byte[] key: writeCache.getModified()) { + V value = super.get(key); + if (value == null) { + + ret |= flushChild(key, value); + if (getSource() != null) { + getSource().delete(key); + } + } else if (value.getSource() != null){ + ret |= flushChild(key, value); + } else { + getSource().put(key, value); + ret = true; + } + } + return ret; + } + + + protected boolean flushChild(byte[] key, V childCache) { + return childCache != null ? childCache.flush() : true; + } + + + protected abstract V create(byte[] key, V srcCache); +} diff --git a/storage/src/main/java/org.platon.storage/datasource/NoDeleteSource.java b/storage/src/main/java/org.platon.storage/datasource/NoDeleteSource.java new file mode 100644 index 0000000..43ae343 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/NoDeleteSource.java @@ -0,0 +1,28 @@ +package org.platon.storage.datasource; + +public class NoDeleteSource extends AbstractChainedSource { + + public NoDeleteSource(Source src) { + super(src); + setFlushSource(true); + } + + @Override + public void delete(Key key) { + } + + @Override + public void put(Key key, Value val) { + if (val != null) getSource().put(key, val); + } + + @Override + public Value get(Key key) { + return getSource().get(key); + } + + @Override + protected boolean flushImpl() { + return false; + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/NodeKeyCompositor.java b/storage/src/main/java/org.platon.storage/datasource/NodeKeyCompositor.java new file mode 100644 index 0000000..83d7aa0 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/NodeKeyCompositor.java @@ -0,0 +1,50 @@ +package org.platon.storage.datasource; + +import org.platon.crypto.HashUtil; + +import static java.lang.System.arraycopy; + +public class NodeKeyCompositor implements SerializerIfc { + + public static final int HASH_LEN = 32; + public static final int PREFIX_BYTES = 16; + private byte[] addrHash; + + public NodeKeyCompositor(byte[] addrOrHash) { + this.addrHash = addrHash(addrOrHash); + } + + @Override + public byte[] serialize(byte[] key) { + return composeInner(key, addrHash); + } + + @Override + public byte[] deserialize(byte[] stream) { + return stream; + } + + public static byte[] compose(byte[] key, byte[] addrOrHash) { + return composeInner(key, addrHash(addrOrHash)); + } + + private static byte[] composeInner(byte[] key, byte[] addrHash) { + + validateKey(key); + + byte[] derivative = new byte[key.length]; + arraycopy(key, 0, derivative, 0, PREFIX_BYTES); + arraycopy(addrHash, 0, derivative, PREFIX_BYTES, PREFIX_BYTES); + + return derivative; + } + + private static void validateKey(byte[] key) { + if (key.length != HASH_LEN) + throw new IllegalArgumentException("Key is not a hash code"); + } + + private static byte[] addrHash(byte[] addrOrHash) { + return addrOrHash.length == HASH_LEN ? addrOrHash : HashUtil.sha3(addrOrHash); + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/PrefixLookupSource.java b/storage/src/main/java/org.platon.storage/datasource/PrefixLookupSource.java new file mode 100644 index 0000000..3af39ec --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/PrefixLookupSource.java @@ -0,0 +1,33 @@ +package org.platon.storage.datasource; + +public class PrefixLookupSource implements Source { + + + private int prefixBytes; + private DbSource source; + + public PrefixLookupSource(DbSource source, int prefixBytes) { + this.source = source; + this.prefixBytes = prefixBytes; + } + + @Override + public V get(byte[] key) { + return source.prefixLookup(key, prefixBytes); + } + + @Override + public void put(byte[] key, V val) { + source.put(key, val); + } + + @Override + public void delete(byte[] key) { + source.delete(key); + } + + @Override + public boolean flush() { + return source.flush(); + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/ReadCache.java b/storage/src/main/java/org.platon.storage/datasource/ReadCache.java new file mode 100644 index 0000000..c4f22d8 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/ReadCache.java @@ -0,0 +1,129 @@ +package org.platon.storage.datasource; + +import org.apache.commons.collections4.map.LRUMap; +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.ByteArrayMap; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ReadCache extends AbstractCachedSource { + + private final Value NULL = (Value) new Object(); + + private Map cache; + private boolean byteKeyMap; + + public ReadCache(Source src) { + super(src); + withCache(new HashMap()); + } + + + public ReadCache withCache(Map cache) { + byteKeyMap = cache instanceof ByteArrayMap; + this.cache = Collections.synchronizedMap(cache); + return this; + } + + + public ReadCache withMaxCapacity(int maxCapacity) { + return withCache(new LRUMap(maxCapacity) { + @Override + protected boolean removeLRU(LinkEntry entry) { + cacheRemoved(entry.getKey(), entry.getValue()); + return super.removeLRU(entry); + } + }); + } + + + private boolean checked = false; + private void checkByteArrKey(Key key) { + if (checked) return; + + if (key instanceof byte[]) { + if (!byteKeyMap) { + throw new RuntimeException("Wrong map/set for byte[] key"); + } + } + checked = true; + } + + @Override + public void put(Key key, Value val) { + checkByteArrKey(key); + if (val == null) { + delete(key); + } else { + cache.put(key, val); + cacheAdded(key, val); + getSource().put(key, val); + } + } + + @Override + public Value get(Key key) { + checkByteArrKey(key); + Value ret = cache.get(key); + if (ret == NULL) { + return null; + } + if (ret == null) { + ret = getSource().get(key); + cache.put(key, ret == null ? NULL : ret); + cacheAdded(key, ret); + } + return ret; + } + + @Override + public void delete(Key key) { + checkByteArrKey(key); + Value value = cache.remove(key); + cacheRemoved(key, value); + getSource().delete(key); + } + + @Override + protected boolean flushImpl() { + return false; + } + + public synchronized Collection getModified() { + return Collections.emptyList(); + } + + @Override + public boolean hasModified() { + return false; + } + + @Override + public synchronized Entry getCached(Key key) { + Value value = cache.get(key); + return value == null ? null : new SimpleEntry<>(value == NULL ? null : value); + } + + + public static class BytesKey extends ReadCache implements CachedSource.BytesKey { + + public BytesKey(Source src) { + super(src); + withCache(new ByteArrayMap()); + } + + public ReadCache.BytesKey withMaxCapacity(int maxCapacity) { + withCache(new ByteArrayMap(new LRUMap(maxCapacity) { + @Override + protected boolean removeLRU(LinkEntry entry) { + cacheRemoved(entry.getKey().getData(), entry.getValue()); + return super.removeLRU(entry); + } + })); + return this; + } + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/ReadWriteCache.java b/storage/src/main/java/org.platon.storage/datasource/ReadWriteCache.java new file mode 100644 index 0000000..8ed41e2 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/ReadWriteCache.java @@ -0,0 +1,54 @@ +package org.platon.storage.datasource; + +import java.util.Collection; + +public class ReadWriteCache + extends SourceChainBox + implements CachedSource { + + protected ReadCache readCache; + protected WriteCache writeCache; + + protected ReadWriteCache(Source source) { + super(source); + } + + public ReadWriteCache(Source src, WriteCache.CacheType cacheType) { + super(src); + add(writeCache = new WriteCache<>(src, cacheType)); + add(readCache = new ReadCache<>(writeCache)); + readCache.setFlushSource(true); + } + + @Override + public synchronized Collection getModified() { + return writeCache.getModified(); + } + + @Override + public boolean hasModified() { + return writeCache.hasModified(); + } + + protected synchronized AbstractCachedSource.Entry getCached(Key key) { + AbstractCachedSource.Entry v = readCache.getCached(key); + if (v == null) { + v = writeCache.getCached(key); + } + return v; + } + + @Override + public synchronized long estimateCacheSize() { + return readCache.estimateCacheSize() + writeCache.estimateCacheSize(); + } + + public static class BytesKey extends ReadWriteCache { + public BytesKey(Source src, WriteCache.CacheType cacheType) { + super(src); + add(this.writeCache = new WriteCache.BytesKey<>(src, cacheType)); + add(this.readCache = new ReadCache.BytesKey<>(writeCache)); + readCache.setFlushSource(true); + } + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/SerializerIfc.java b/storage/src/main/java/org.platon.storage/datasource/SerializerIfc.java new file mode 100644 index 0000000..6eba1a7 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/SerializerIfc.java @@ -0,0 +1,8 @@ +package org.platon.storage.datasource; + +public interface SerializerIfc { + + S serialize(T object); + + T deserialize(S stream); +} diff --git a/storage/src/main/java/org.platon.storage/datasource/Source.java b/storage/src/main/java/org.platon.storage/datasource/Source.java new file mode 100644 index 0000000..bc88d28 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/Source.java @@ -0,0 +1,18 @@ +package org.platon.storage.datasource; + + +public interface Source { + + + void put(K key, V value); + + + V get(K key); + + + void delete(K key); + + + boolean flush(); + +} diff --git a/storage/src/main/java/org.platon.storage/datasource/SourceChainBox.java b/storage/src/main/java/org.platon.storage/datasource/SourceChainBox.java new file mode 100644 index 0000000..05a945c --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/SourceChainBox.java @@ -0,0 +1,40 @@ +package org.platon.storage.datasource; + +import java.util.ArrayList; +import java.util.List; + +public class SourceChainBox + extends AbstractChainedSource { + + List chain = new ArrayList<>(); + Source lastSource; + + public SourceChainBox(Source source) { + super(source); + } + + public void add(Source src) { + chain.add(src); + lastSource = src; + } + + @Override + public void put(Key key, Value val) { + lastSource.put(key, val); + } + + @Override + public Value get(Key key) { + return lastSource.get(key); + } + + @Override + public void delete(Key key) { + lastSource.delete(key); + } + + @Override + protected boolean flushImpl() { + return lastSource.flush(); + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/WriteCache.java b/storage/src/main/java/org.platon.storage/datasource/WriteCache.java new file mode 100644 index 0000000..20d677b --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/WriteCache.java @@ -0,0 +1,270 @@ +package org.platon.storage.datasource; + +import com.googlecode.concurentlocks.ReadWriteUpdateLock; +import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock; +import org.platon.storage.utils.AutoLock; +import org.platon.common.utils.ByteArrayMap; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class WriteCache extends AbstractCachedSource { + + + public enum CacheType { + + SIMPLE, + + COUNTING + } + + private static abstract class CacheEntry implements Entry{ + + + static final Object UNKNOWN_VALUE = new Object(); + + V value; + int counter = 0; + + protected CacheEntry(V value) { + this.value = value; + } + + protected abstract void deleted(); + + protected abstract void added(); + + protected abstract V getValue(); + + @Override + public V value() { + V v = getValue(); + return v == UNKNOWN_VALUE ? null : v; + } + } + + private static final class SimpleCacheEntry extends CacheEntry { + public SimpleCacheEntry(V value) { + super(value); + } + + public void deleted() { + counter = -1; + } + + public void added() { + counter = 1; + } + + @Override + public V getValue() { + return counter < 0 ? null : value; + } + } + + private static final class CountCacheEntry extends CacheEntry { + public CountCacheEntry(V value) { + super(value); + } + + public void deleted() { + counter--; + } + + public void added() { + counter++; + } + + @Override + public V getValue() { + + + + return value; + } + } + + private final boolean isCounting; + + protected volatile Map> cache = new HashMap<>(); + + protected ReadWriteUpdateLock rwuLock = new ReentrantReadWriteUpdateLock(); + protected AutoLock readLock = new AutoLock(rwuLock.readLock()); + protected AutoLock writeLock = new AutoLock(rwuLock.writeLock()); + protected AutoLock updateLock = new AutoLock(rwuLock.updateLock()); + + private boolean checked = false; + + public WriteCache(Source src, CacheType cacheType) { + super(src); + this.isCounting = cacheType == CacheType.COUNTING; + } + + public WriteCache withCache(Map> cache) { + this.cache = cache; + return this; + } + + @Override + public Collection getModified() { + try (AutoLock l = readLock.lock()){ + return cache.keySet(); + } + } + + @Override + public boolean hasModified() { + return !cache.isEmpty(); + } + + private CacheEntry createCacheEntry(Value val) { + if (isCounting) { + return new CountCacheEntry<>(val); + } else { + return new SimpleCacheEntry<>(val); + } + } + + @Override + public void put(Key key, Value val) { + checkByteArrKey(key); + if (val == null) { + delete(key); + return; + } + + + try (AutoLock l = writeLock.lock()){ + CacheEntry curVal = cache.get(key); + if (curVal == null) { + curVal = createCacheEntry(val); + CacheEntry oldVal = cache.put(key, curVal); + if (oldVal != null) { + + cacheRemoved(key, oldVal.value == unknownValue() ? null : oldVal.value); + } + cacheAdded(key, curVal.value); + } + + + curVal.value = val; + curVal.added(); + } + } + + @Override + public Value get(Key key) { + checkByteArrKey(key); + try (AutoLock l = readLock.lock()){ + CacheEntry curVal = cache.get(key); + if (curVal == null) { + return getSource() == null ? null : getSource().get(key); + } else { + Value value = curVal.getValue(); + if (value == unknownValue()) { + return getSource() == null ? null : getSource().get(key); + } else { + return value; + } + } + } + } + + @Override + public void delete(Key key) { + checkByteArrKey(key); + try (AutoLock l = writeLock.lock()){ + CacheEntry curVal = cache.get(key); + if (curVal == null) { + curVal = createCacheEntry(getSource() == null ? null : unknownValue()); + CacheEntry oldVal = cache.put(key, curVal); + if (oldVal != null) { + cacheRemoved(key, oldVal.value); + } + cacheAdded(key, curVal.value == unknownValue() ? null : curVal.value); + } + curVal.deleted(); + } + } + + @Override + public boolean flush() { + boolean ret = false; + try (AutoLock l = updateLock.lock()){ + for (Map.Entry> entry : cache.entrySet()) { + if (entry.getValue().counter > 0) { + for (int i = 0; i < entry.getValue().counter; i++) { + getSource().put(entry.getKey(), entry.getValue().value); + } + ret = true; + } else if (entry.getValue().counter < 0) { + for (int i = 0; i > entry.getValue().counter; i--) { + getSource().delete(entry.getKey()); + } + ret = true; + } + } + if (flushSource) { + getSource().flush(); + } + try (AutoLock l1 = writeLock.lock()){ + cache.clear(); + cacheCleared(); + } + return ret; + } + } + + @Override + protected boolean flushImpl() { + return false; + } + + private Value unknownValue() { + return (Value) CacheEntry.UNKNOWN_VALUE; + } + + public Entry getCached(Key key) { + try (AutoLock l = readLock.lock()){ + CacheEntry entry = cache.get(key); + if (entry == null || entry.value == unknownValue()) { + return null; + }else { + return entry; + } + } + } + + + + + private void checkByteArrKey(Key key) { + if (checked) return; + + if (key instanceof byte[]) { + if (!(cache instanceof ByteArrayMap)) { + throw new RuntimeException("Wrong map/set for byte[] key"); + } + } + checked = true; + } + + public long debugCacheSize() { + long ret = 0; + for (Map.Entry> entry : cache.entrySet()) { + ret += keySizeEstimator.estimateSize(entry.getKey()); + ret += valueSizeEstimator.estimateSize(entry.getValue().value()); + } + return ret; + } + + + public static class BytesKey extends WriteCache implements CachedSource.BytesKey { + + public BytesKey(Source src, CacheType cacheType) { + super(src, cacheType); + withCache(new ByteArrayMap>()); + } + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/XorDataSource.java b/storage/src/main/java/org.platon.storage/datasource/XorDataSource.java new file mode 100644 index 0000000..23d6b6b --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/XorDataSource.java @@ -0,0 +1,37 @@ +package org.platon.storage.datasource; + +import org.platon.common.utils.ByteUtil; + +public class XorDataSource extends AbstractChainedSource { + + private byte[] subKey; + + public XorDataSource(Source source, byte[] subKey) { + super(source); + this.subKey = subKey; + } + + private byte[] convertKey(byte[] key) { + return ByteUtil.xorAlignRight(key, subKey); + } + + @Override + public V get(byte[] key) { + return getSource().get(convertKey(key)); + } + + @Override + public void put(byte[] key, V value) { + getSource().put(convertKey(key), value); + } + + @Override + public void delete(byte[] key) { + getSource().delete(convertKey(key)); + } + + @Override + protected boolean flushImpl() { + return false; + } +} diff --git a/storage/src/main/java/org.platon.storage/datasource/inmemory/HashMapDB.java b/storage/src/main/java/org.platon.storage/datasource/inmemory/HashMapDB.java new file mode 100644 index 0000000..9a2e282 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/inmemory/HashMapDB.java @@ -0,0 +1,137 @@ +package org.platon.storage.datasource.inmemory; + +import org.platon.common.utils.ByteComparator; +import org.platon.storage.datasource.DbSettings; +import org.platon.storage.datasource.DbSource; +import org.platon.storage.utils.AutoLock; +import org.platon.common.utils.ByteArrayMap; + +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class HashMapDB implements DbSource { + + protected final Map storage; + private String name ; + private ReadWriteLock rwLock = new ReentrantReadWriteLock(); + private AutoLock readLock = new AutoLock(rwLock.readLock()); + private AutoLock writeLock = new AutoLock(rwLock.writeLock()); + + public HashMapDB(ByteArrayMap storage) { + this.storage = storage; + } + + public HashMapDB() { + this(new ByteArrayMap()); + } + + @Override + public void open() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void open(DbSettings settings) throws RuntimeException { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isAlive() { + return true; + } + + @Override + public void close() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public Set keys() throws RuntimeException { + try (AutoLock loc = readLock.lock()) { + Set result = new LinkedHashSet<>(); + Set keys = getStorage().keySet(); + Iterator iterator = keys.iterator(); + while(iterator.hasNext()){ + result.add(iterator.next()); + } + return result; + } + } + + @Override + public void reset() { + try (AutoLock lock = writeLock.lock()) { + storage.clear(); + } + } + + @Override + public V prefixLookup(byte[] key, int prefixBytes) throws RuntimeException { + try (AutoLock l = readLock.lock()) { + for (Map.Entry entry : getStorage().entrySet()) { + if (ByteComparator.compareTo(key, 0, prefixBytes, entry.getKey(), 0, prefixBytes) == 0){ + return entry.getValue(); + } + } + } + return null; + } + + @Override + public String getName() { + return "in-memory"; + } + + @Override + public void updateBatch(Map rows){ + try (AutoLock lock = writeLock.lock()) { + for (Map.Entry entry : rows.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public void put(byte[] key, V value) { + if (value == null) { + delete(key); + }else{ + try (AutoLock lock = writeLock.lock()) { + getStorage().put(key, value); + } + } + } + + @Override + public V get(byte[] key) { + try (AutoLock lock = readLock.lock()) { + return getStorage().get(key); + } + } + + @Override + public void delete(byte[] key) { + try(AutoLock lock = writeLock.lock()){ + getStorage().remove(key); + } + } + + @Override + public boolean flush() { + return true; + } + + public Map getStorage() { + return storage; + } + +} diff --git a/storage/src/main/java/org.platon.storage/datasource/leveldb/LevelDBSource.java b/storage/src/main/java/org.platon.storage/datasource/leveldb/LevelDBSource.java new file mode 100644 index 0000000..5a8054e --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/leveldb/LevelDBSource.java @@ -0,0 +1,247 @@ +package org.platon.storage.datasource.leveldb; + +import org.iq80.leveldb.*; +import org.iq80.leveldb.impl.Iq80DBFactory; +import org.platon.common.AppenderName; +import org.platon.common.utils.FileUtil; +import org.platon.common.utils.Numeric; +import org.platon.storage.datasource.DbSettings; +import org.platon.storage.datasource.DbSource; +import org.platon.storage.utils.AutoLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class LevelDBSource implements DbSource { + + Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_DB); + + private ReadWriteLock dbLock = new ReentrantReadWriteLock(); + private AutoLock readLock = new AutoLock(dbLock.readLock()); + private AutoLock writeLock = new AutoLock(dbLock.writeLock()); + + private DBFactory factory = Iq80DBFactory.factory; + private DbSettings settings = DbSettings.DEFAULT; + private String dbPath; + private DB db; + private boolean alive; + private String name; + + public LevelDBSource(String name, String dbPath) { + this.name = name; + this.dbPath = dbPath; + logger.debug("create new leveldb ,db name:{},db path:{}", name, dbPath); + } + + @Override + public void open() throws RuntimeException { + open(settings); + } + + @Override + public void open(DbSettings settings) throws RuntimeException { + this.settings = settings; + dbLock.writeLock().lock(); + try { + if (isAlive()) return; + if (name == null) { + throw new NullPointerException("db name is null"); + } + + Options options = new Options(); + + options.createIfMissing(true); + + options.errorIfExists(false); + + + + options.maxOpenFiles(this.settings.getMaxOpenFiles()); + + + + + + + + + + + + + File dbFile = new File(dbPath + File.separator + name); + try { + db = factory.open(dbFile, options); + } catch (IOException ioe) { + + if (ioe.getMessage().contains("Corruption:")) { + logger.warn("levelDB is corrupted, db name:" + name, ioe); + logger.info("Trying to repair, db name:" + name); + factory.repair(dbFile, options); + logger.info("Repair finished, db name:" + name); + db = factory.open(dbFile, options); + } else { + throw ioe; + } + } + alive = true; + } catch (Exception e) { + logger.error("open db error, db name:" + name, e); + throw new RuntimeException(e.getMessage()); + } finally { + dbLock.writeLock().unlock(); + } + } + + @Override + public void close() throws Exception { + try (AutoLock lock = writeLock.lock()) { + if (!isAlive()) { + return; + } + if (logger.isDebugEnabled()) { + logger.debug("For close db, name : {}", name); + } + db.close(); + alive = false; + } catch (Exception e) { + logger.error("close db error, db name:" + name, e); + throw e; + } + } + + @Override + public void updateBatch(Map rows) { + try (AutoLock lock = writeLock.lock()) { + if (logger.isTraceEnabled()) { + logger.trace("~> LevelDBStorage.batchUpdate(): " + name + ", " + rows.size()); + } + WriteBatch batch = db.createWriteBatch(); + for (Map.Entry entry : rows.entrySet()) { + if (entry.getValue() == null) { + batch.delete(entry.getKey()); + } else { + batch.put(entry.getKey(), entry.getValue()); + } + } + db.write(batch); + if (logger.isTraceEnabled()) { + logger.trace("<~ LevelDBStorage.batchUpdate(): " + name + ", " + rows.size()); + } + } + } + + @Override + public boolean isAlive() { + return alive; + } + + @Override + public Set keys() { + try (AutoLock lock = readLock.lock()) { + if (logger.isTraceEnabled()) { + logger.trace("~> LevelDBStorage.keys(): " + name); + } + DBIterator iterator = db.iterator(); + Set result = new LinkedHashSet<>(); + for (iterator.seekToFirst(); iterator.hasNext(); iterator.next()) { + result.add(iterator.peekNext().getKey()); + } + if (logger.isTraceEnabled()) { + logger.trace("<~ LevelDBStorage.keys(): " + name); + } + return result; + } + } + + @Override + public void reset() { + try { + String pathStr = (getDbPath() + File.separator + name); + if (logger.isDebugEnabled()) { + logger.debug("Do reset, name is: {}, db abs path: {}", name, pathStr); + } + close(); + FileUtil.recursiveDelete(pathStr); + open(settings); + } catch (Exception e) { + logger.error("Do reset, throw exception.", e); + } + } + + @Override + public byte[] prefixLookup(byte[] key, int prefixBytes) throws RuntimeException { + throw new RuntimeException("The method of prefixLookup is not supported"); + } + + @Override + public void put(byte[] key, byte[] val) { + try (AutoLock lock = writeLock.lock()) { + if (logger.isTraceEnabled()) { + logger.trace("~> LevelDBStorage.put(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (val == null ? "null" : val.length)); + } + db.put(key, val); + if (logger.isTraceEnabled()) { + logger.trace("<~ LevelDBStorage.put(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (val == null ? "null" : val.length)); + } + } + } + + @Override + public byte[] get(byte[] key) { + try (AutoLock lock = readLock.lock()) { + if (logger.isTraceEnabled()) { + logger.trace("~> LevelDBStorage.get(): " + name + ", key: " + Numeric.toHexString(key)); + } + byte[] response = db.get(key); + if (logger.isTraceEnabled()) { + logger.trace("<~ LevelDBStorage.get(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (response == null ? "null" : response.length)); + } + return response; + } catch (DBException e) { + logger.error("~> LevelDBStorage.get() throw exception.", e); + } + return null; + } + + @Override + public void delete(byte[] key) { + try (AutoLock lock = readLock.lock()) { + if (logger.isTraceEnabled()) { + logger.trace("~> LevelDBStorage.get(): " + name + ", key: " + Numeric.toHexString(key)); + } + db.delete(key); + if (logger.isTraceEnabled()) { + logger.trace("<~ LevelDBStorage.get(): " + name + ", key: " + Numeric.toHexString(key)); + } + } + db.delete(key); + } + + @Override + public boolean flush() { + + return false; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDbPath() { + return dbPath; + } + +} diff --git a/storage/src/main/java/org.platon.storage/datasource/rocksdb/RocksDBSource.java b/storage/src/main/java/org.platon.storage/datasource/rocksdb/RocksDBSource.java new file mode 100644 index 0000000..95ed515 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/datasource/rocksdb/RocksDBSource.java @@ -0,0 +1,241 @@ +package org.platon.storage.datasource.rocksdb; + +import org.platon.common.AppenderName; +import org.platon.common.utils.FileUtil; +import org.platon.common.utils.Numeric; +import org.platon.storage.datasource.DbSettings; +import org.platon.storage.datasource.DbSource; +import org.platon.storage.utils.AutoLock; +import org.rocksdb.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class RocksDBSource implements DbSource { + + private Logger logger = LoggerFactory.getLogger(AppenderName.APPENDER_DB); + + private ReadWriteLock dbLock = new ReentrantReadWriteLock(); + private AutoLock readLock = new AutoLock(dbLock.readLock()); + private AutoLock writeLock = new AutoLock(dbLock.writeLock()); + + private DbSettings settings = DbSettings.DEFAULT; + private String dbPath; + private RocksDB db; + private boolean alive; + private String name; + + static { + RocksDB.loadLibrary(); + } + + public RocksDBSource(String name, String dbPath) { + this.name = name; + this.dbPath = dbPath; + if (logger.isDebugEnabled()) { + logger.debug("create new rocksdb , db name: " + name + ", dbPath = " + dbPath); + } + } + + @PostConstruct + @Override + public void open() { + open(DbSettings.DEFAULT); + } + + @Override + public void open(DbSettings settings) throws RuntimeException { + this.settings = settings; + try (AutoLock lock = writeLock.lock()) { + + if (isAlive()) { + return; + } + if (name == null) { + throw new NullPointerException("Required db name."); + } + try { + + Options options = new Options(); + options.setMaxOpenFiles(this.settings.getMaxOpenFiles()); + options.setIncreaseParallelism(settings.getMaxThreads()); + options.useFixedLengthPrefixExtractor(16); + + + + + + + + + + + options.setCreateIfMissing(true); + + options.setSkipStatsUpdateOnDbOpen(true); + + options.setLevelCompactionDynamicLevelBytes(true); + + String pathStr = dbPath + File.separator + name; + if(!new File(pathStr).exists()){ + new File(pathStr).mkdirs(); + } + db = RocksDB.open(options, dbPath + File.separator + name); + logger.debug("db is open,db name:" + name); + alive = true; + } catch (Exception e) { + logger.error("open db error,db name: " + name, e); + throw new RuntimeException("init rocks db failed."); + } + } + } + + @Override + public void close() throws Exception { + try (AutoLock lock = writeLock.lock()) { + if (!isAlive()) { + return; + } + db.close(); + if (logger.isDebugEnabled()) { + logger.debug("db is close,db name:" + name); + } + alive = false; + } catch (Exception e) { + logger.error("close db error,db name: " + name, e); + } + } + + @Override + public boolean isAlive() { + return alive; + } + + + @Override + public void updateBatch(Map rows) { + try (AutoLock lock = writeLock.lock()) { + traceLogPrintln("~> RocksDbSource.batchUpdate(): " + name + ", " + rows.size()); + WriteBatch batch = new WriteBatch(); + WriteOptions writeOptions = new WriteOptions(); + for (Map.Entry entry : rows.entrySet()) { + if (entry.getValue() == null) { + batch.delete(entry.getKey()); + } else { + batch.put(entry.getKey(), entry.getValue()); + } + } + db.write(writeOptions, batch); + traceLogPrintln("<~ RocksDbSource.batchUpdate(): " + name + ", " + rows.size()); + } catch (Exception e) { + logger.error("batchUpdate error,db name: " + name, e); + } + } + + @Override + public void put(byte[] key, byte[] val) { + try (AutoLock lock = readLock.lock()) { + traceLogPrintln("~> RocksDbSource.put(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (val == null ? "null" : val.length)); + if (val == null) { + db.delete(key); + } else { + db.put(key, val); + } + traceLogPrintln("<~ RocksDbSource.put(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (val == null ? "null" : val.length)); + } catch (Exception e) { + logger.error("RocksDbSource put error, db name: " + name, e); + } + } + + @Override + public byte[] get(byte[] key) { + try(AutoLock lock = readLock.lock()) { + traceLogPrintln("~> RocksDbSource.get(): " + name + ", key: " + Numeric.toHexString(key)); + byte[] res = db.get(key); + traceLogPrintln("<~ RocksDbSource.get(): " + name + ", key: " + Numeric.toHexString(key) + ", " + (res == null ? "null" : res.length)); + return res; + } catch (Exception e) { + logger.error("RocksDbSource get error, db name: " + name, e); + throw new RuntimeException(e); + } + } + + @Override + public void delete(byte[] key) { + try(AutoLock lock = readLock.lock()) { + traceLogPrintln("~> RocksDbSource.delete(): " + name + ", key: " + Numeric.toHexString(key)); + db.delete(key); + traceLogPrintln("<~ RocksDbDataSource.delete(): " + name + ", key: " + Numeric.toHexString(key)); + } catch (Exception e) { + logger.error("delete error,db name: " + name, e); + } + } + + @Override + public boolean flush() { + return false; + } + + @Override + public Set keys() { + try(AutoLock lock = readLock.lock()){ + traceLogPrintln("~> RocksDBStorage1.keys(): {}.", name); + RocksIterator iterator = db.newIterator(); + Set result = new LinkedHashSet<>(); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + result.add(iterator.key()); + } + traceLogPrintln("<~ RocksDBStorage1.keys(): {}, {} ", name, result.size()); + return result; + }catch (Exception e){ + logger.error("RocksDBStorage1.keys(): throw exception.", e); + throw new RuntimeException(e); + } + } + + @Override + public void reset() { + try { + String pathStr = (getDbPath() + File.separator + name); + traceLogPrintln("RocksDBStorage1.reset(): name is: {}, db abs path:{}", name, pathStr); + close(); + FileUtil.recursiveDelete(pathStr); + open(settings); + }catch (Exception e){ + logger.error("RocksDBStorage1.reset() throw exception. ", e); + throw new RuntimeException(e); + } + } + + @Override + public byte[] prefixLookup(byte[] key, int prefixBytes) throws RuntimeException { + return new byte[0]; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private void traceLogPrintln(String format, Object... arguments){ + if (logger.isTraceEnabled()) { + logger.trace(format, arguments); + } + } + + public String getDbPath() { + return dbPath; + } + +} diff --git a/storage/src/main/java/org.platon.storage/enums/OverlimitStrategyEnum.java b/storage/src/main/java/org.platon.storage/enums/OverlimitStrategyEnum.java new file mode 100644 index 0000000..0065c6d --- /dev/null +++ b/storage/src/main/java/org.platon.storage/enums/OverlimitStrategyEnum.java @@ -0,0 +1,41 @@ +package org.platon.storage.enums; + +public enum OverlimitStrategyEnum { + + OVERLIMIT_STRATEGY_COMMIT(1,"超限策略-提交"), + OVERLIMIT_STRATEGY_THROW_EXCEPTION(2,"超限策略-报错"); + + private String name; + private int code; + + private OverlimitStrategyEnum(int code, String name){ + this.name = name; + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public static String getNameByCodeValue(int code){ + OverlimitStrategyEnum[] allEnums = values(); + for(OverlimitStrategyEnum enableStatus : allEnums){ + if(enableStatus.getCode()==code){ + return enableStatus.getName(); + } + } + return null; + } +} diff --git a/storage/src/main/java/org.platon.storage/exception/OverlimitException.java b/storage/src/main/java/org.platon.storage/exception/OverlimitException.java new file mode 100644 index 0000000..34ac83b --- /dev/null +++ b/storage/src/main/java/org.platon.storage/exception/OverlimitException.java @@ -0,0 +1,17 @@ +package org.platon.storage.exception; + + +public class OverlimitException extends RuntimeException{ + + public OverlimitException(String message){ + super(message); + } + + public OverlimitException(String message, Throwable cause) { + super(message, cause); + } + + public OverlimitException(Throwable cause) { + super(cause); + } +} diff --git a/storage/src/main/java/org.platon.storage/trie/SecureTrie.java b/storage/src/main/java/org.platon.storage/trie/SecureTrie.java new file mode 100644 index 0000000..bf70cd7 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/trie/SecureTrie.java @@ -0,0 +1,34 @@ +package org.platon.storage.trie; + +import org.platon.storage.datasource.Source; + +import static org.platon.common.utils.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.platon.crypto.HashUtil.sha3; + +public class SecureTrie extends TrieImpl { + + public SecureTrie(Source cache) { + super(cache, true, 32); + } + + public SecureTrie(Source cache, byte[] root) { + super(cache, true, 32, root); + } + + public SecureTrie(){} + + @Override + public byte[] get(byte[] key) { + return super.get(sha3(key)); + } + + @Override + public void put(byte[] key, byte[] value) { + super.put(sha3(key), value); + } + + @Override + public void delete(byte[] key) { + put(key, EMPTY_BYTE_ARRAY); + } +} diff --git a/storage/src/main/java/org.platon.storage/trie/Trie.java b/storage/src/main/java/org.platon.storage/trie/Trie.java new file mode 100644 index 0000000..36ffed7 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/trie/Trie.java @@ -0,0 +1,12 @@ +package org.platon.storage.trie; + +import org.platon.storage.datasource.Source; + +public interface Trie extends Source { + + byte[] getRootHash(); + + void setRoot(byte[] root); + + void clear(); +} diff --git a/storage/src/main/java/org.platon.storage/trie/TrieImpl.java b/storage/src/main/java/org.platon.storage/trie/TrieImpl.java new file mode 100644 index 0000000..a5ddb57 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/trie/TrieImpl.java @@ -0,0 +1,1061 @@ +package org.platon.storage.trie; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.util.encoders.Hex; +import org.platon.core.proto.EmptyBytesMessage; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.Source; +import org.platon.storage.datasource.inmemory.HashMapDB; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TrieImpl implements Trie { + + public static final byte[] EMPTY_TRIE_HASH ; + + private final static Object NULL_NODE = new Object(); + private final byte ENCODEISHASH = 't'; + private final byte ENCODEISNOTHASH = 'f'; + + static { + EMPTY_TRIE_HASH = HashUtil.sha3(EmptyBytesMessage.newBuilder().setData(ByteString.EMPTY).build().toByteArray()); + } + + public class Node { + protected byte[] hash; + protected byte[] key; + protected byte[] lazyParse; + protected boolean dirty; + + + public byte[] encode() { + return encode(true); + } + + + private byte[] encode(boolean forceHash) { + int len; + byte isHash; + byte[] ret; + + if (!dirty) { + if (provable || this == root) { + return hash; + } else { + if (hash == null && lazyParse != null && lazyParse.length > 0 && lazyParse.length < byteSplitThreshold) { + len = lazyParse.length; + ret = new byte[len + 1]; + System.arraycopy(lazyParse, 0, ret, 1, len); + ret[0] = ENCODEISNOTHASH; + return ret; + } else if (hash != null) { + len = hash.length; + ret = new byte[len + 1]; + System.arraycopy(hash, 0, ret, 1, len); + ret[0] = ENCODEISHASH; + return ret; + } else { + throw new RuntimeException("Node status error"); + } + } + } + + TrieProto.NodeBase.Builder builder = TrieProto.NodeBase.newBuilder(); + + if (key != null && key.length > 0) { + builder.setKey(ByteString.copyFrom(key)); + } + + if (this instanceof ValueNode) { + ValueNode vNode = (ValueNode) this; + if (vNode.value != null && vNode.value.length > 0) { + builder.addValueOrNodeHash(ByteString.copyFrom(vNode.value)); + } + } else if (this instanceof BranchNode) { + BranchNode bNode = (BranchNode) this; + for (Node node : bNode.childNodes) { + node.parse(); + if (node != null) { + builder.addValueOrNodeHash(ByteString.copyFrom(node.encode(false))); + } else { + throw new RuntimeException("Should not get null child node!"); + } + } + } else { + throw new RuntimeException("Should not use Node instance to call any method!"); + } + + if (hash != null) { + deleteHash(hash); + hash = null; + } + dirty = false; + + TrieProto.NodeBase nodeBase = builder.build(); + byte[] byteArr = nodeBase.toByteArray(); + + if (provable || this == root) { + hash = bcSHA3Digest256(byteArr); + addHash(hash, byteArr); + return hash; + } else { + + if (byteArr.length < byteSplitThreshold && !forceHash) { + len = byteArr.length; + isHash = ENCODEISNOTHASH; + lazyParse = byteArr; + } else { + hash = bcSHA3Digest256(byteArr); + addHash(hash, byteArr); + len = hash.length; + isHash = ENCODEISHASH; + byteArr = hash; + } + ret = new byte[len + 1]; + ret[0] = isHash; + System.arraycopy(byteArr, 0, ret, 1, len); + return ret; + } + } + + private boolean resolveCheck() { + if (lazyParse != null || hash == null) { + return true; + } + + lazyParse = getHash(hash); + return lazyParse != null; + } + + public void resolve() { + if (!resolveCheck()) { + throw new RuntimeException("Invalid Trie state, can't resolve hash " + new String(hash)); + } + } + + public void dispose() { + if (hash != null) { + deleteHash(hash); + } + } + + public void parse() { + parse(0); + } + + private void parse(int depth) { + if (this instanceof ValueNode) { + ValueNode vNode = (ValueNode) this; + if (vNode.value != null) { + return; + } + } else if (this instanceof BranchNode) { + BranchNode bNode = (BranchNode) this; + if (bNode.childNodes != null && bNode.childNodes.size() > 1) { + return; + } + } else { + throw new RuntimeException("Unexpected Node instance Node"); + } + + if (hash == null && lazyParse == null) { + throw new RuntimeException("Could not parse:hash=null, lazyParse=null"); + } else if (hash != null && lazyParse == null) { + resolve(); + } else { + } + + try { + TrieProto.NodeBase nodeBase = TrieProto.NodeBase.parseFrom(lazyParse); + + int cnt = nodeBase.getValueOrNodeHashCount(); + assert cnt > 0 && cnt < 257; + key = nodeBase.getKey().toByteArray(); + + + if (cnt == 1) { + ValueNode vNode = (ValueNode) this; + vNode.value = nodeBase.getValueOrNodeHash(0).toByteArray(); + } else { + if (depth >= 1) { + return; + } + + int index; + byte isHash; + BranchNode bNode = (BranchNode) this; + for (index = 0; index < cnt; index++) { + + byte[] hashOrValue = nodeBase.getValueOrNodeHash(index).toByteArray(); + assert hashOrValue.length > 1; + byte[] hOrV = new byte[hashOrValue.length - 1]; + byte[] childLazyParse; + + if (!provable) { + isHash = hashOrValue[0]; + System.arraycopy(hashOrValue, 1, hOrV, 0, hashOrValue.length - 1); + + if (isHash == ENCODEISNOTHASH) { + childLazyParse = hOrV; + } else { + childLazyParse = getHash(hOrV); + } + } else { + childLazyParse = getHash(hashOrValue); + } + + int childrenOfChild = TrieImpl.parseNodeType(childLazyParse); + + Node childNode; + if (childrenOfChild == 1) { + childNode = new ValueNode(); + } else if (childrenOfChild >= 2) { + childNode = new BranchNode(); + } else { + throw new RuntimeException("Invalid encoded Trie "); + } + childNode.lazyParse = childLazyParse; + childNode.parse(depth + 1); + bNode.childNodes.add(childNode); + } + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + + public String getType() { + if (this instanceof ValueNode) { + return new String("V:"); + } else if (this instanceof BranchNode) { + return new String("B:"); + } else { + return new String("N:"); + } + } + + public String dumpStruct(String indent, String prefix) { + String ret; + String hashStr = ""; + String keyStr = ""; + final int SUBLENGTHLIMIT = 8; + if (hash != null) { + hashStr = Hex.toHexString(hash); + } + if (key != null) { + keyStr = Hex.toHexString(key); + } + if (this instanceof ValueNode) { + ValueNode vThis = (ValueNode) this; + String valudeStr = Hex.toHexString(vThis.value); + ret = indent + prefix + "ValueNode" + (dirty ? " *" : "") + + (hash == null ? "" : "(hash: " + hashStr.substring(0, SUBLENGTHLIMIT) + ")"); + ret += " [" + keyStr.substring(0, Math.min(SUBLENGTHLIMIT, keyStr.length())) + "] = " + valudeStr.substring(0, Math.min(SUBLENGTHLIMIT, valudeStr.length())) + "\n"; + } else if (this instanceof BranchNode) { + BranchNode bThis = (BranchNode) this; + bThis.parse(); + ret = indent + prefix + "BranchNode" + (dirty ? " *" : "") + + (hash == null ? "" : "(hash: " + hashStr.substring(0, Math.min(SUBLENGTHLIMIT, hashStr.length())) + ")") + + (key == null ? "" : "(key: " + keyStr.substring(0, Math.min(SUBLENGTHLIMIT, keyStr.length())) + ")"); + + for (int i = 0; i < bThis.childNodes.size(); i++) { + Node child = bThis.getChildNodeByIndex(i); + if (child != null) { + ret += child.dumpStruct(indent + " ", "[" + i + "] "); + } + } + } else { + ret = indent + prefix + "Node" + (dirty ? " *" : "") + + (hash == null ? "" : "(hash: " + hashStr.substring(0, SUBLENGTHLIMIT) + ")"); + } + return ret; + } + + public List dumpTrieNode(boolean compact) { + List ret = new ArrayList<>(); + if (hash != null) { + ret.add(hash2str(hash, compact) + " ==> " + dumpContent(false, compact)); + } + + if (this instanceof BranchNode) { + BranchNode bThis = (BranchNode) this; + bThis.parse(); + for (int i = 0; i < bThis.childNodes.size(); i++) { + Node child = bThis.getChildNodeByIndex(i); + if (child != null) { + ret.addAll(child.dumpTrieNode(compact)); + } + } + } else if (this instanceof ValueNode) { + + } + return ret; + } + + private String dumpContent(boolean recursion, boolean compact) { + if (recursion && hash != null) { + return hash2str(hash, compact); + } + + String ret; + if (this instanceof BranchNode) { + ret = "["; + BranchNode bThis = (BranchNode) this; + bThis.parse(); + for (int i = 0; i < bThis.childNodes.size(); i++) { + Node child = bThis.getChildNodeByIndex(i); + ret += i == 0 ? "" : ","; + ret += child == null ? "" : child.dumpContent(true, compact); + } + ret += "]"; + } else if (this instanceof BranchNode) { + ValueNode vThis = (ValueNode) this; + ret = "[, " + val2str(vThis.value, compact) + "]"; + } else { + ret = "[]"; + } + return ret; + } + } + + public final class ValueNode extends Node { + protected byte[] value; + + public ValueNode() { + dirty = true; + } + + public ValueNode(byte[] hash) { + this.hash = hash; + dirty = true; + } + + public ValueNode(byte[] key, byte[] value) { + this.key = key; + this.value = value; + dirty = true; + } + + public byte[] getValue() { + parse(); + return value; + } + + public Node setValue(byte[] value) { + parse(); + assert value != null; + this.value = value; + dirty = true; + return this; + } + } + + public final class BranchNode extends Node { + + protected ArrayList childNodes; + + public BranchNode(byte[] key) { + this.key = key; + this.childNodes = new ArrayList(); + dirty = true; + } + + public BranchNode() { + this.childNodes = new ArrayList(); + dirty = true; + } + + public Node getChildNodeByIndex(int index) { + parse(); + return childNodes.get(index); + } + + public int getChildPositionBy1stByte(int searchByte) { + parse(); + Object n = NULL_NODE; + int left = 0; + int right = childNodes.size() - 1; + int mid, midNodeByte; + + while (left <= right) { + mid = (left + right) / 2; + midNodeByte = childNodes.get(mid).key[0] & 0xff; + if (searchByte < midNodeByte) { + right = mid - 1; + } else if (midNodeByte < searchByte) { + left = mid + 1; + } else { + return mid; + } + } + + return -1; + } + + public int getByteInsertPosition(int searchByte) { + parse(); + Object n = NULL_NODE; + int left = 0; + int right = childNodes.size() - 1; + int mid, midNodeByte, pos = -1; + if (childNodes.size() == 0) { + return 0; + } else if (childNodes.size() == 1) { + return searchByte < (childNodes.get(0).key[0] & 0xff) ? 0 : 1; + } + + + mid = (left + right) / 2; + while (left < right) { + mid = (left + right) / 2; + midNodeByte = childNodes.get(mid).key[0] & 0xff; + if (searchByte < midNodeByte) { + right = mid; + } else if (searchByte > midNodeByte) { + left = mid; + } else { + + throw new RuntimeException("Should not exist!"); + } + if (left == right - 1) { + break; + } + } + + + if (searchByte < (childNodes.get(left).key[0] & 0xff)) { + return left; + } else if (searchByte > (childNodes.get(right).key[0] & 0xff)) { + return right + 1; + } else { + return left + 1; + } + } + } + + private boolean provable = false; + private int byteSplitThreshold = 32; + + private Node root; + private Source cache; + + + public TrieImpl() { + this.cache = new HashMapDB(); + provable = true; + } + + + public TrieImpl(boolean useSPV, int threshold) { + this.cache = new HashMapDB(); + provable = useSPV; + byteSplitThreshold = threshold; + } + + public TrieImpl(Source cache, boolean useSPV, int threshold) { + this.cache = cache; + provable = useSPV; + byteSplitThreshold = threshold; + } + + public TrieImpl(Source cache, boolean useSPV, int threshold, byte[] root) { + this.cache = cache; + provable = useSPV; + byteSplitThreshold = threshold; + setRoot(root); + } + + public Source getCache() { + return cache; + } + + + @Override + public void setRoot(byte[] rootHash) { + if (rootHash != null) { + byte[] ret = getHash(rootHash); + int cnt = TrieImpl.parseNodeType(ret); + if (cnt == 1) { + this.root = new ValueNode(rootHash); + this.root.lazyParse = ret; + this.root.parse(); + } else if (cnt >= 2) { + this.root = new BranchNode(); + this.root.hash = rootHash; + this.root.lazyParse = ret; + this.root.parse(); + } else { + this.root = null; + } + } else { + this.root = null; + } + } + + @Override + public void clear() { + throw new RuntimeException("Not Unsupported."); + } + + private void encode() { + if (root != null) { + root.encode(); + } + } + + private boolean hasRoot() { + return root != null && root.resolveCheck(); + } + + @Override + public byte[] getRootHash() { + encode(); + if (hasRoot()) { + return root.hash; + } else { + return HashUtil.EMPTY_HASH; + } + } + + public void setRootHash(byte[] hash) { + root.hash = hash; + root.dirty = true; + } + + private int calcCommonPrefix(byte[] k1, byte[] k2) { + int len, commLen = 0; + + if (k1 == null || k2 == null) { + return 0; + } + + len = k1.length <= k2.length ? k1.length : k2.length; + for (int i = 0; i < len; i++) { + if (k1[i] == k2[i]) + commLen++; + else + return commLen; + } + return commLen; + } + + public byte[] spvEncodeHashList(byte[] spvKey) { + if (!provable || spvKey == null) { + return null; + } + + if (get(spvKey) == null) { + return null; + } + + return spvEncode(root, bcSHA3Digest256(spvKey), true); + } + + public byte[] spvEncodeHashList(byte[] spvKey, byte[] spvValue) { + if (!provable || spvKey == null || spvValue == null) { + return null; + } + + if (!Arrays.equals(get(spvKey), spvValue)) + { + return null; + } + + return spvEncode(root, bcSHA3Digest256(spvKey), true); + } + + private byte[] spvEncode(Node n, byte[] k, boolean hasCommon) { + + if (!hasCommon) { + return n.encode(); + } + + if (n instanceof ValueNode) { + assert Arrays.equals(n.key, k); + + ValueNode vThis = (ValueNode) n; + TrieProto.NodeBase.Builder builder = TrieProto.NodeBase.newBuilder(); + builder.setKey(ByteString.copyFrom(n.key)); + builder.addValueOrNodeHash(ByteString.copyFrom(vThis.getValue())); + builder.setHash(ByteString.copyFrom(n.encode())); + + TrieProto.NodeBase nodeBase = builder.build(); + return nodeBase.toByteArray(); + } else if (n instanceof BranchNode) { + int commPrefix = calcCommonPrefix(n.key, k); + byte[] kLeft = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kLeft, 0, k.length - commPrefix); + + BranchNode bThis = (BranchNode) n; + int cnt = bThis.childNodes.size(); + TrieProto.NodeBase.Builder builder = TrieProto.NodeBase.newBuilder(); + for (int i = 0; i < cnt; i++) { + byte[] childRet; + Node child = bThis.getChildNodeByIndex(i); + child.parse(); + if (child.key[0] == kLeft[0]) { + childRet = spvEncode(child, kLeft, true); + TrieProto.NodeBase childNb; + try { + childNb = TrieProto.NodeBase.parseFrom(childRet); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Decode proto error:!" + new String(childRet)); + } + + builder.setChildBase(childNb); + builder.setChildBasePos(i); + builder.setChildEncode(ByteString.copyFrom(childRet)); + } else { + childRet = spvEncode(child, kLeft, false); + builder.addValueOrNodeHash(ByteString.copyFrom(childRet)); + } + } + + builder.setKey(ByteString.copyFrom(n.key)); + builder.setHash(ByteString.copyFrom(n.encode())); + + TrieProto.NodeBase nodeBase = builder.build(); + return nodeBase.toByteArray(); + } else { + throw new RuntimeException("Invalid Node type"); + } + } + + public static boolean spvVerifyHashList(byte[] spvHash, byte[] data) { + try { + TrieProto.NodeBase nodeBase = TrieProto.NodeBase.parseFrom(data); + + TrieProto.NodeBase.Builder builder = TrieProto.NodeBase.newBuilder(); + builder.setKey(nodeBase.getKey()); + byte[] nodeHash = nodeBase.getHash().toByteArray(); + + int pos; + if (nodeBase.getChildBase() == TrieProto.NodeBase.getDefaultInstance()) { + if (nodeBase.getValueOrNodeHashCount() != 1) { + throw new RuntimeException("Data decode error "); + } else { + builder.addValueOrNodeHash(nodeBase.getValueOrNodeHash(0)); + TrieProto.NodeBase childNode = builder.build(); + byte[] childByteArr = childNode.toByteArray(); + byte[] caculNodeHash = bcSHA3Digest256(childByteArr); + + + byte[] caculedSPVHash = bcSHA3Digest256(nodeBase.getValueOrNodeHash(0).toByteArray()); + return Arrays.equals(caculedSPVHash, spvHash) && Arrays.equals(nodeHash, caculNodeHash); + } + } else { + pos = nodeBase.getChildBasePos(); + int cnt = nodeBase.getValueOrNodeHashCount(); + for (int i = 0; i < pos; i++) { + builder.addValueOrNodeHash(nodeBase.getValueOrNodeHash(i)); + } + + TrieProto.NodeBase childBase = nodeBase.getChildBase(); + byte[] childHash = childBase.getHash().toByteArray(); + builder.addValueOrNodeHash(ByteString.copyFrom(childHash)); + for (int i = 0; i < cnt - pos; i++) { + builder.addValueOrNodeHash(nodeBase.getValueOrNodeHash(i + pos)); + } + } + + TrieProto.NodeBase thisNode = builder.build(); + byte[] byteArr = thisNode.toByteArray(); + if (!Arrays.equals(bcSHA3Digest256(byteArr), nodeHash)) { + return false; + } + + byte[] childData = nodeBase.getChildEncode().toByteArray(); + + return spvVerifyHashList(spvHash, childData); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Decode spv data error "); + } + + } + + + public static byte[] bcSHA3Digest256(byte[] value) { + SHA3.DigestSHA3 digestSHA3 = new SHA3.Digest256(); + return digestSHA3.digest(value); + } + + public byte[] get(byte[] key) { + if (hasRoot()) { + return get(root, bcSHA3Digest256(key)); + } + return null; + } + + public byte[] get(byte[] key, boolean isHash) { + if (hasRoot()) { + if (isHash && key != null && key.length == 32) { + return get(root, key); + } else { + return get(root, bcSHA3Digest256(key)); + } + } + return null; + } + + private byte[] get(Node n, byte[] k) { + if (n == null || k == null) { + return null; + } + + if (n instanceof ValueNode) { + if (Arrays.equals(n.key, k)) { + ValueNode vNode = (ValueNode) n; + return vNode.getValue(); + } else { + return null; + } + } else if (n instanceof BranchNode) { + int commPrefix = calcCommonPrefix(n.key, k); + if (commPrefix == k.length) { + return null; + } else { + int index = k[commPrefix] & 0xff; + byte[] kLeft = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kLeft, 0, k.length - commPrefix); + BranchNode bNode = (BranchNode) n; + int pos = bNode.getChildPositionBy1stByte(index); + if (pos == -1) { + return null; + } + return get(bNode.getChildNodeByIndex(pos), kLeft); + } + } else { + throw new RuntimeException("Invalid Trie type, can't resolve type "); + } + } + + public void put(byte[] key, byte[] value) { + if (key != null && key.length > 0 && value != null && value.length > 0) { + if (root == null) { + root = new ValueNode(bcSHA3Digest256(key), value); + } else { + root = insert(root, bcSHA3Digest256(key), value); + } + } else { + + } + } + + public void put(byte[] key, byte[] value, boolean isHash) { + if (key != null && key.length > 0 && value != null && value.length > 0) { + byte[] kNew; + if (isHash && key != null && key.length == 32) { + kNew = key; + } else { + kNew = bcSHA3Digest256(key); + } + + if (root == null) { + root = new ValueNode(kNew, value); + } else { + root = insert(root, kNew, value); + } + } else { + + } + } + + private Node insert(Node n, byte[] k, byte[] v) { + if (n == null || k == null || v == null) { + + return null; + } + + int commPrefix = calcCommonPrefix(n.key, k); + + if (n instanceof ValueNode) { + if (commPrefix == n.key.length) { + ValueNode vNode = (ValueNode) n; + return vNode.setValue(v); + } else if (commPrefix == k.length) { + throw new RuntimeException("Invalid key length:" + new String(k)); + } else { + byte[] commKey = new byte[commPrefix]; + System.arraycopy(k, 0, commKey, 0, commPrefix); + BranchNode newBranchNode = new BranchNode(commKey); + + byte[] nKeyNew = new byte[n.key.length - commPrefix]; + System.arraycopy(n.key, commPrefix, nKeyNew, 0, n.key.length - commPrefix); + ValueNode nNode = (ValueNode) n; + ValueNode nNewNode = new ValueNode(nKeyNew, nNode.value); + int posN = nKeyNew[0] & 0xff; + + byte[] kNew = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kNew, 0, k.length - commPrefix); + ValueNode newKVNode = new ValueNode(kNew, v); + int posK = kNew[0] & 0xff; + + if (posN < posK) { + newBranchNode.childNodes.add(nNewNode); + newBranchNode.childNodes.add(newKVNode); + } else { + newBranchNode.childNodes.add(newKVNode); + newBranchNode.childNodes.add(nNewNode); + } + + return newBranchNode; + } + } else if (n instanceof BranchNode) { + if (n.key.length == 0 || commPrefix == n.key.length) { + byte[] kNew = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kNew, 0, k.length - commPrefix); + + int index = kNew[0] & 0xff; + BranchNode b = (BranchNode) n; + int pos = b.getChildPositionBy1stByte(index); + if (pos == -1) { + ValueNode newKVNode = new ValueNode(kNew, v); + int posInsert = b.getByteInsertPosition(index); + b.childNodes.add(posInsert, newKVNode); + } else { + Node child = b.getChildNodeByIndex(pos); + Node retNode = insert(child, kNew, v); + if (retNode != child) { + b.childNodes.remove(pos); + b.childNodes.add(pos, retNode); + } + } + return b; + } else { + byte[] commKey = new byte[commPrefix]; + System.arraycopy(k, 0, commKey, 0, commPrefix); + BranchNode newBranchNode = new BranchNode(commKey); + + byte[] kNew = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kNew, 0, k.length - commPrefix); + ValueNode newKVNode = new ValueNode(kNew, v); + int posK = kNew[0] & 0xff; + + byte[] nKeyNew = new byte[n.key.length - commPrefix]; + System.arraycopy(n.key, commPrefix, nKeyNew, 0, n.key.length - commPrefix); + BranchNode nNewNode = (BranchNode) n; + nNewNode.key = nKeyNew; + int posN = nKeyNew[0] & 0xff; + + if (posN < posK) { + newBranchNode.childNodes.add(nNewNode); + newBranchNode.childNodes.add(newKVNode); + } else { + newBranchNode.childNodes.add(newKVNode); + newBranchNode.childNodes.add(nNewNode); + } + + return newBranchNode; + } + } else { + throw new RuntimeException("Invalid Trie type, can't resolve type "); + } + } + + public void delete(byte[] key) { + if (root != null) { + root = delete(root, bcSHA3Digest256(key)); + } + } + + public void delete(byte[] key, boolean isHash) { + if (root != null) { + if (isHash && key != null && key.length == 32) { + root = delete(root, key); + } else { + root = delete(root, bcSHA3Digest256(key)); + } + } + } + + private Node delete(Node n, byte[] k) { + if (n instanceof ValueNode) { + if (Arrays.equals(n.key, k)) { + n.dispose(); + if (root.hash != null && root.hash.length > 0 && Arrays.equals(n.hash, root.hash)) { + root = null; + } + + return null; + } else { + return n; + } + } else if (n instanceof BranchNode) { + int commPrefix = calcCommonPrefix(n.key, k); + + assert k.length > commPrefix; + + byte[] kNew = new byte[k.length - commPrefix]; + System.arraycopy(k, commPrefix, kNew, 0, k.length - commPrefix); + int index = kNew[0] & 0xff; + BranchNode bNode = (BranchNode) n; + int posNode = bNode.getChildPositionBy1stByte(index); + if (posNode == -1) { + return n; + } else { + Node child = bNode.getChildNodeByIndex(posNode); + Node newChild = delete(child, kNew); + if (child == newChild) { + return n; + } else if (newChild != null) { + bNode.childNodes.remove(posNode); + bNode.childNodes.add(posNode, newChild); + return bNode; + } else { + bNode.childNodes.remove(posNode); + } + + int pos = 0; + int childNum = 0; + BranchNode bnNode = (BranchNode) n; + int i = -1; + for (Node children : bNode.childNodes) { + i++; + if (children instanceof ValueNode) { + ValueNode vChild = (ValueNode) children; + if (vChild != null && vChild.value != null) { + childNum++; + pos = i; + } + } else if (children instanceof BranchNode) { + BranchNode bChild = (BranchNode) children; + if (bChild != null && bChild.childNodes != null && bChild.childNodes.size() > 1) { + childNum++; + pos = i; + } + } + + if (childNum >= 2) { + + return n; + } + } + + if (childNum == 0) { + n.dispose(); + return null; + } else { + Node childNew = bnNode.childNodes.get(pos); + byte[] keyNew = new byte[n.key.length + childNew.key.length]; + System.arraycopy(n.key, 0, keyNew, 0, n.key.length); + System.arraycopy(childNew.key, 0, keyNew, n.key.length, childNew.key.length); + childNew.key = keyNew; + childNew.dirty = true; + + n.dispose(); + remove(n); + return childNew; + } + } + } else { + throw new RuntimeException("Invalid Trie type, can't resolve type "); + } + } + + private void remove(Node n) { + n.hash = null; + n.key = null; + n.lazyParse = null; + + if (n instanceof ValueNode) { + ValueNode vn = (ValueNode) n; + vn.value = null; + } else if (n instanceof BranchNode) { + BranchNode bn = (BranchNode) n; + bn.childNodes = null; + } else { + throw new RuntimeException("Invalid Trie type, can't resolve type "); + } + n = null; + } + + public boolean flush() { + if (root != null && root.dirty) { + + encode(); + + if (root instanceof ValueNode) { + root = new ValueNode(root.hash); + } else if (root instanceof BranchNode) { + byte[] oldHash = root.hash; + root = new BranchNode(); + root.hash = oldHash; + } else { + throw new RuntimeException("Invalid Node type"); + } + + return true; + } else { + return false; + } + } + + private void addHash(byte[] hash, byte[] value) { + cache.put(hash, value); + } + + private byte[] getHash(byte[] hash) { + return cache.get(hash); + } + + private void deleteHash(byte[] hash) { + cache.delete(hash); + } + + + public String dumpStructure() { + return root == null ? "" : root.dumpStruct("", ""); + } + + public String dumpTrie() { + return dumpTrie(true); + } + + public String dumpTrie(boolean compact) { + if (root == null) return ""; + encode(); + StringBuilder ret = new StringBuilder(); + List strings = root.dumpTrieNode(compact); + ret.append("Root: " + hash2str(getRootHash(), compact) + "\n"); + for (String s : strings) { + ret.append(s).append('\n'); + } + return ret.toString(); + } + + private static String hash2str(byte[] hash, boolean shortHash) { + String ret = new String(hash); + return "0x" + (shortHash ? ret.substring(0, 8) : ret); + } + + private static String val2str(byte[] val, boolean shortHash) { + String ret = new String(val); + if (val.length > 16) { + ret = ret.substring(0, 10) + "... len " + val.length; + } + return "\"" + ret + "\""; + } + + private static int parseNodeType(byte[] codedData) { + int cnt = -1; + if (codedData != null && codedData.length > 0) { + try { + TrieProto.NodeBase nodeBase = TrieProto.NodeBase.parseFrom(codedData); + cnt = nodeBase.getValueOrNodeHashCount(); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Fail while decode" + new String(codedData)); + } + } else { + cnt = -2; + } + return cnt; + } +} diff --git a/storage/src/main/java/org.platon.storage/trie/TrieKey.java b/storage/src/main/java/org.platon.storage/trie/TrieKey.java new file mode 100644 index 0000000..c4ed370 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/trie/TrieKey.java @@ -0,0 +1,164 @@ +package org.platon.storage.trie; + +import static org.platon.common.utils.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.platon.common.utils.Numeric.toHexString; + +public final class TrieKey { + public static final int ODD_OFFSET_FLAG = 0x1; + public static final int TERMINATOR_FLAG = 0x2; + private final byte[] key; + private final int off; + private final boolean terminal; + + public static TrieKey fromNormal(byte[] key) { + return new TrieKey(key); + } + + public static TrieKey fromPacked(byte[] key) { + return new TrieKey(key, ((key[0] >> 4) & ODD_OFFSET_FLAG) != 0 ? 1 : 2, ((key[0] >> 4) & TERMINATOR_FLAG) != 0); + } + + public static TrieKey empty(boolean terminal) { + return new TrieKey(EMPTY_BYTE_ARRAY, 0, terminal); + } + + public static TrieKey singleHex(int hex) { + TrieKey ret = new TrieKey(new byte[1], 1, false); + ret.setHex(0, hex); + return ret; + } + + public TrieKey(byte[] key, int off, boolean terminal) { + this.terminal = terminal; + this.off = off; + this.key = key; + } + + private TrieKey(byte[] key) { + this(key, 0, true); + } + + public byte[] toPacked() { + int flags = ((off & 1) != 0 ? ODD_OFFSET_FLAG : 0) | (terminal ? TERMINATOR_FLAG : 0); + byte[] ret = new byte[getLength() / 2 + 1]; + int toCopy = (flags & ODD_OFFSET_FLAG) != 0 ? ret.length : ret.length - 1; + System.arraycopy(key, key.length - toCopy, ret, ret.length - toCopy, toCopy); + ret[0] &= 0x0F; + ret[0] |= flags << 4; + return ret; + } + + public byte[] toNormal() { + if ((off & 1) != 0) throw new RuntimeException("Can't convert a key with odd number of hexes to normal: " + this); + int arrLen = key.length - off / 2; + byte[] ret = new byte[arrLen]; + System.arraycopy(key, key.length - arrLen, ret, 0, arrLen); + return ret; + } + + public boolean isTerminal() { + return terminal; + } + + public boolean isEmpty() { + return getLength() == 0; + } + + public TrieKey shift(int hexCnt) { + return new TrieKey(this.key, off + hexCnt, terminal); + } + + public TrieKey getCommonPrefix(TrieKey k) { + + int prefixLen = 0; + int thisLength = getLength(); + int kLength = k.getLength(); + while (prefixLen < thisLength && prefixLen < kLength && getHex(prefixLen) == k.getHex(prefixLen)) + prefixLen++; + byte[] prefixKey = new byte[(prefixLen + 1) >> 1]; + TrieKey ret = new TrieKey(prefixKey, (prefixLen & 1) == 0 ? 0 : 1, + prefixLen == getLength() && prefixLen == k.getLength() && terminal && k.isTerminal()); + for (int i = 0; i < prefixLen; i++) { + ret.setHex(i, k.getHex(i)); + } + return ret; + } + + public TrieKey matchAndShift(TrieKey k) { + int len = getLength(); + int kLen = k.getLength(); + if (len < kLen) return null; + + if ((off & 1) == (k.off & 1)) { + + if ((off & 1) == 1) { + if (getHex(0) != k.getHex(0)) return null; + } + int idx1 = (off + 1) >> 1; + int idx2 = (k.off + 1) >> 1; + int l = kLen >> 1; + for (int i = 0; i < l; i++, idx1++, idx2++) { + if (key[idx1] != k.key[idx2]) return null; + } + } else { + for (int i = 0; i < kLen; i++) { + if (getHex(i) != k.getHex(i)) return null; + } + } + return shift(kLen); + } + + public int getLength() { + return (key.length << 1) - off; + } + + private void setHex(int idx, int hex) { + int byteIdx = (off + idx) >> 1; + if (((off + idx) & 1) == 0) { + key[byteIdx] &= 0x0F; + key[byteIdx] |= hex << 4; + } else { + key[byteIdx] &= 0xF0; + key[byteIdx] |= hex; + } + } + + public int getHex(int idx) { + byte b = key[(off + idx) >> 1]; + return (((off + idx) & 1) == 0 ? (b >> 4) : b) & 0xF; + } + + public TrieKey concat(TrieKey k) { + if (isTerminal()) throw new RuntimeException("Can' append to terminal key: " + this + " + " + k); + int len = getLength(); + int kLen = k.getLength(); + int newLen = len + kLen; + byte[] newKeyBytes = new byte[(newLen + 1) >> 1]; + TrieKey ret = new TrieKey(newKeyBytes, newLen & 1, k.isTerminal()); + for (int i = 0; i < len; i++) { + ret.setHex(i, getHex(i)); + } + for (int i = 0; i < kLen; i++) { + ret.setHex(len + i, k.getHex(i)); + } + return ret; + } + + @Override + public boolean equals(Object obj) { + TrieKey k = (TrieKey) obj; + int len = getLength(); + + if (len != k.getLength()) return false; + + for (int i = 0; i < len; i++) { + if (getHex(i) != k.getHex(i)) return false; + } + return isTerminal() == k.isTerminal(); + } + + @Override + public String toString() { + return toHexString(key).substring(off) + (isTerminal() ? "T" : ""); + } +} diff --git a/storage/src/main/java/org.platon.storage/trie/TrieProto.java b/storage/src/main/java/org.platon.storage/trie/TrieProto.java new file mode 100644 index 0000000..aeaa0eb --- /dev/null +++ b/storage/src/main/java/org.platon.storage/trie/TrieProto.java @@ -0,0 +1,961 @@ + + + +package org.platon.storage.trie; + +public final class TrieProto { + private TrieProto() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface NodeBaseOrBuilder extends + + com.google.protobuf.MessageOrBuilder { + + + com.google.protobuf.ByteString getHash(); + + + com.google.protobuf.ByteString getKey(); + + + java.util.List getValueOrNodeHashList(); + + int getValueOrNodeHashCount(); + + com.google.protobuf.ByteString getValueOrNodeHash(int index); + + + boolean hasChildBase(); + + NodeBase getChildBase(); + + NodeBaseOrBuilder getChildBaseOrBuilder(); + + + com.google.protobuf.ByteString getChildEncode(); + + + int getChildBasePos(); + } + + public static final class NodeBase extends + com.google.protobuf.GeneratedMessageV3 implements + + NodeBaseOrBuilder { + private static final long serialVersionUID = 0L; + + private NodeBase(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private NodeBase() { + hash_ = com.google.protobuf.ByteString.EMPTY; + key_ = com.google.protobuf.ByteString.EMPTY; + valueOrNodeHash_ = java.util.Collections.emptyList(); + childEncode_ = com.google.protobuf.ByteString.EMPTY; + childBasePos_ = 0; + } + + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private NodeBase( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownFieldProto3( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + + hash_ = input.readBytes(); + break; + } + case 18: { + + key_ = input.readBytes(); + break; + } + case 26: { + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + valueOrNodeHash_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + valueOrNodeHash_.add(input.readBytes()); + break; + } + case 34: { + Builder subBuilder = null; + if (childBase_ != null) { + subBuilder = childBase_.toBuilder(); + } + childBase_ = input.readMessage(NodeBase.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(childBase_); + childBase_ = subBuilder.buildPartial(); + } + + break; + } + case 42: { + + childEncode_ = input.readBytes(); + break; + } + case 48: { + + childBasePos_ = input.readInt32(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + valueOrNodeHash_ = java.util.Collections.unmodifiableList(valueOrNodeHash_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return TrieProto.internal_static_NodeBase_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return TrieProto.internal_static_NodeBase_fieldAccessorTable + .ensureFieldAccessorsInitialized( + NodeBase.class, Builder.class); + } + + private int bitField0_; + public static final int HASH_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString hash_; + + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + public static final int KEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString key_; + + public com.google.protobuf.ByteString getKey() { + return key_; + } + + public static final int VALUEORNODEHASH_FIELD_NUMBER = 3; + private java.util.List valueOrNodeHash_; + + public java.util.List + getValueOrNodeHashList() { + return valueOrNodeHash_; + } + + public int getValueOrNodeHashCount() { + return valueOrNodeHash_.size(); + } + + public com.google.protobuf.ByteString getValueOrNodeHash(int index) { + return valueOrNodeHash_.get(index); + } + + public static final int CHILDBASE_FIELD_NUMBER = 4; + private NodeBase childBase_; + + public boolean hasChildBase() { + return childBase_ != null; + } + + public NodeBase getChildBase() { + return childBase_ == null ? NodeBase.getDefaultInstance() : childBase_; + } + + public NodeBaseOrBuilder getChildBaseOrBuilder() { + return getChildBase(); + } + + public static final int CHILDENCODE_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString childEncode_; + + public com.google.protobuf.ByteString getChildEncode() { + return childEncode_; + } + + public static final int CHILDBASEPOS_FIELD_NUMBER = 6; + private int childBasePos_; + + public int getChildBasePos() { + return childBasePos_; + } + + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!hash_.isEmpty()) { + output.writeBytes(1, hash_); + } + if (!key_.isEmpty()) { + output.writeBytes(2, key_); + } + for (int i = 0; i < valueOrNodeHash_.size(); i++) { + output.writeBytes(3, valueOrNodeHash_.get(i)); + } + if (childBase_ != null) { + output.writeMessage(4, getChildBase()); + } + if (!childEncode_.isEmpty()) { + output.writeBytes(5, childEncode_); + } + if (childBasePos_ != 0) { + output.writeInt32(6, childBasePos_); + } + unknownFields.writeTo(output); + } + + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!hash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, hash_); + } + if (!key_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, key_); + } + { + int dataSize = 0; + for (int i = 0; i < valueOrNodeHash_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(valueOrNodeHash_.get(i)); + } + size += dataSize; + size += 1 * getValueOrNodeHashList().size(); + } + if (childBase_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, getChildBase()); + } + if (!childEncode_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, childEncode_); + } + if (childBasePos_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(6, childBasePos_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof NodeBase)) { + return super.equals(obj); + } + NodeBase other = (NodeBase) obj; + + boolean result = true; + result = result && getHash() + .equals(other.getHash()); + result = result && getKey() + .equals(other.getKey()); + result = result && getValueOrNodeHashList() + .equals(other.getValueOrNodeHashList()); + result = result && (hasChildBase() == other.hasChildBase()); + if (hasChildBase()) { + result = result && getChildBase() + .equals(other.getChildBase()); + } + result = result && getChildEncode() + .equals(other.getChildEncode()); + result = result && (getChildBasePos() + == other.getChildBasePos()); + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + if (getValueOrNodeHashCount() > 0) { + hash = (37 * hash) + VALUEORNODEHASH_FIELD_NUMBER; + hash = (53 * hash) + getValueOrNodeHashList().hashCode(); + } + if (hasChildBase()) { + hash = (37 * hash) + CHILDBASE_FIELD_NUMBER; + hash = (53 * hash) + getChildBase().hashCode(); + } + hash = (37 * hash) + CHILDENCODE_FIELD_NUMBER; + hash = (53 * hash) + getChildEncode().hashCode(); + hash = (37 * hash) + CHILDBASEPOS_FIELD_NUMBER; + hash = (53 * hash) + getChildBasePos(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static NodeBase parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static NodeBase parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static NodeBase parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static NodeBase parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static NodeBase parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static NodeBase parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static NodeBase parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static NodeBase parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static NodeBase parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static NodeBase parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static NodeBase parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static NodeBase parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(NodeBase prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + + NodeBaseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return TrieProto.internal_static_NodeBase_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return TrieProto.internal_static_NodeBase_fieldAccessorTable + .ensureFieldAccessorsInitialized( + NodeBase.class, Builder.class); + } + + + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + public Builder clear() { + super.clear(); + hash_ = com.google.protobuf.ByteString.EMPTY; + + key_ = com.google.protobuf.ByteString.EMPTY; + + valueOrNodeHash_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + if (childBaseBuilder_ == null) { + childBase_ = null; + } else { + childBase_ = null; + childBaseBuilder_ = null; + } + childEncode_ = com.google.protobuf.ByteString.EMPTY; + + childBasePos_ = 0; + + return this; + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return TrieProto.internal_static_NodeBase_descriptor; + } + + public NodeBase getDefaultInstanceForType() { + return NodeBase.getDefaultInstance(); + } + + public NodeBase build() { + NodeBase result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public NodeBase buildPartial() { + NodeBase result = new NodeBase(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + result.hash_ = hash_; + result.key_ = key_; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + valueOrNodeHash_ = java.util.Collections.unmodifiableList(valueOrNodeHash_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.valueOrNodeHash_ = valueOrNodeHash_; + if (childBaseBuilder_ == null) { + result.childBase_ = childBase_; + } else { + result.childBase_ = childBaseBuilder_.build(); + } + result.childEncode_ = childEncode_; + result.childBasePos_ = childBasePos_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder clone() { + return (Builder) super.clone(); + } + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return (Builder) super.setField(field, value); + } + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + Object value) { + return (Builder) super.addRepeatedField(field, value); + } + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof NodeBase) { + return mergeFrom((NodeBase)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(NodeBase other) { + if (other == NodeBase.getDefaultInstance()) return this; + if (other.getHash() != com.google.protobuf.ByteString.EMPTY) { + setHash(other.getHash()); + } + if (other.getKey() != com.google.protobuf.ByteString.EMPTY) { + setKey(other.getKey()); + } + if (!other.valueOrNodeHash_.isEmpty()) { + if (valueOrNodeHash_.isEmpty()) { + valueOrNodeHash_ = other.valueOrNodeHash_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureValueOrNodeHashIsMutable(); + valueOrNodeHash_.addAll(other.valueOrNodeHash_); + } + onChanged(); + } + if (other.hasChildBase()) { + mergeChildBase(other.getChildBase()); + } + if (other.getChildEncode() != com.google.protobuf.ByteString.EMPTY) { + setChildEncode(other.getChildEncode()); + } + if (other.getChildBasePos() != 0) { + setChildBasePos(other.getChildBasePos()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + NodeBase parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (NodeBase) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private com.google.protobuf.ByteString hash_ = com.google.protobuf.ByteString.EMPTY; + + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + public Builder setHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; + + public com.google.protobuf.ByteString getKey() { + return key_; + } + + public Builder setKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + key_ = value; + onChanged(); + return this; + } + + public Builder clearKey() { + + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + + private java.util.List valueOrNodeHash_ = java.util.Collections.emptyList(); + private void ensureValueOrNodeHashIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + valueOrNodeHash_ = new java.util.ArrayList(valueOrNodeHash_); + bitField0_ |= 0x00000004; + } + } + + public java.util.List + getValueOrNodeHashList() { + return java.util.Collections.unmodifiableList(valueOrNodeHash_); + } + + public int getValueOrNodeHashCount() { + return valueOrNodeHash_.size(); + } + + public com.google.protobuf.ByteString getValueOrNodeHash(int index) { + return valueOrNodeHash_.get(index); + } + + public Builder setValueOrNodeHash( + int index, com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureValueOrNodeHashIsMutable(); + valueOrNodeHash_.set(index, value); + onChanged(); + return this; + } + + public Builder addValueOrNodeHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureValueOrNodeHashIsMutable(); + valueOrNodeHash_.add(value); + onChanged(); + return this; + } + + public Builder addAllValueOrNodeHash( + Iterable values) { + ensureValueOrNodeHashIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, valueOrNodeHash_); + onChanged(); + return this; + } + + public Builder clearValueOrNodeHash() { + valueOrNodeHash_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + + private NodeBase childBase_ = null; + private com.google.protobuf.SingleFieldBuilderV3< + NodeBase, Builder, NodeBaseOrBuilder> childBaseBuilder_; + + public boolean hasChildBase() { + return childBaseBuilder_ != null || childBase_ != null; + } + + public NodeBase getChildBase() { + if (childBaseBuilder_ == null) { + return childBase_ == null ? NodeBase.getDefaultInstance() : childBase_; + } else { + return childBaseBuilder_.getMessage(); + } + } + + public Builder setChildBase(NodeBase value) { + if (childBaseBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + childBase_ = value; + onChanged(); + } else { + childBaseBuilder_.setMessage(value); + } + + return this; + } + + public Builder setChildBase( + Builder builderForValue) { + if (childBaseBuilder_ == null) { + childBase_ = builderForValue.build(); + onChanged(); + } else { + childBaseBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + + public Builder mergeChildBase(NodeBase value) { + if (childBaseBuilder_ == null) { + if (childBase_ != null) { + childBase_ = + NodeBase.newBuilder(childBase_).mergeFrom(value).buildPartial(); + } else { + childBase_ = value; + } + onChanged(); + } else { + childBaseBuilder_.mergeFrom(value); + } + + return this; + } + + public Builder clearChildBase() { + if (childBaseBuilder_ == null) { + childBase_ = null; + onChanged(); + } else { + childBase_ = null; + childBaseBuilder_ = null; + } + + return this; + } + + public Builder getChildBaseBuilder() { + + onChanged(); + return getChildBaseFieldBuilder().getBuilder(); + } + + public NodeBaseOrBuilder getChildBaseOrBuilder() { + if (childBaseBuilder_ != null) { + return childBaseBuilder_.getMessageOrBuilder(); + } else { + return childBase_ == null ? + NodeBase.getDefaultInstance() : childBase_; + } + } + + private com.google.protobuf.SingleFieldBuilderV3< + NodeBase, Builder, NodeBaseOrBuilder> + getChildBaseFieldBuilder() { + if (childBaseBuilder_ == null) { + childBaseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + NodeBase, Builder, NodeBaseOrBuilder>( + getChildBase(), + getParentForChildren(), + isClean()); + childBase_ = null; + } + return childBaseBuilder_; + } + + private com.google.protobuf.ByteString childEncode_ = com.google.protobuf.ByteString.EMPTY; + + public com.google.protobuf.ByteString getChildEncode() { + return childEncode_; + } + + public Builder setChildEncode(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + childEncode_ = value; + onChanged(); + return this; + } + + public Builder clearChildEncode() { + + childEncode_ = getDefaultInstance().getChildEncode(); + onChanged(); + return this; + } + + private int childBasePos_ ; + + public int getChildBasePos() { + return childBasePos_; + } + + public Builder setChildBasePos(int value) { + + childBasePos_ = value; + onChanged(); + return this; + } + + public Builder clearChildBasePos() { + + childBasePos_ = 0; + onChanged(); + return this; + } + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFieldsProto3(unknownFields); + } + + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + + } + + + private static final NodeBase DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new NodeBase(); + } + + public static NodeBase getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + public NodeBase parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new NodeBase(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + public NodeBase getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_NodeBase_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_NodeBase_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + String[] descriptorData = { + "\n\021NodeList.v7.proto\"\207\001\n\010NodeBase\022\014\n\004hash" + + "\030\001 \001(\014\022\013\n\003key\030\002 \001(\014\022\027\n\017valueOrNodeHash\030\003" + + " \003(\014\022\034\n\tchildBase\030\004 \001(\0132\t.NodeBase\022\023\n\013ch" + + "ildEncode\030\005 \001(\014\022\024\n\014childBasePos\030\006 \001(\005B-\n" + + " com.juzix.platon.blockchain.trieB\tTrieP" + + "rotob\006proto3" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + internal_static_NodeBase_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_NodeBase_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_NodeBase_descriptor, + new String[] { "Hash", "Key", "ValueOrNodeHash", "ChildBase", "ChildEncode", "ChildBasePos", }); + } + + +} diff --git a/storage/src/main/java/org.platon.storage/utils/AutoLock.java b/storage/src/main/java/org.platon.storage/utils/AutoLock.java new file mode 100644 index 0000000..9479147 --- /dev/null +++ b/storage/src/main/java/org.platon.storage/utils/AutoLock.java @@ -0,0 +1,21 @@ +package org.platon.storage.utils; + +import java.util.concurrent.locks.Lock; + +public final class AutoLock implements AutoCloseable { + + private final Lock lock; + + public AutoLock(Lock l) { + this.lock = l; + } + + public final AutoLock lock() { + this.lock.lock(); + return this; + } + + public final void close() { + this.lock.unlock(); + } +} diff --git a/storage/src/test/java/org/platon/storage/StorageTest.java b/storage/src/test/java/org/platon/storage/StorageTest.java new file mode 100644 index 0000000..46ce294 --- /dev/null +++ b/storage/src/test/java/org/platon/storage/StorageTest.java @@ -0,0 +1,78 @@ +package org.platon.storage; + +import org.platon.common.utils.ByteArrayWrapper; +import org.platon.common.utils.RandomUtils; +import org.platon.storage.datasource.DbSource; +import org.slf4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + + +public class StorageTest { + + public void updateBatch(DbSource storage, Logger logger) { + try { + int batchSize = 100; + Map rows = createBatch(batchSize); + storage.updateBatch(rows); + for (Map.Entry e : rows.entrySet()) { + assertArrayEquals(e.getValue(), rows.get(e.getKey())); + } + } catch (Exception e) { + logger.error("batchUpdate error", e); + assertFalse(true); + } + } + + public void put(DbSource storage, Logger logger) { + try { + byte[] key = RandomUtils.randomBytes(32); + byte[] value = RandomUtils.randomBytes(32); + storage.put(key, value); + assertArrayEquals(value, (byte[]) storage.get(key)); + } catch (Exception e) { + logger.error("put error", e); + assertFalse(true); + } + } + + public void keys(DbSource storage, Logger logger) { + try { + byte[] key = RandomUtils.randomBytes(32); + byte[] value = RandomUtils.randomBytes(32); + storage.put(key, value); + Set keys = storage.keys(); + assertTrue(keys.contains(new ByteArrayWrapper(key))); + } catch (Exception e) { + logger.error("keys error", e); + assertFalse(true); + } + } + + public void delete(DbSource storage, Logger logger) { + try { + byte[] key = RandomUtils.randomBytes(32); + byte[] value = RandomUtils.randomBytes(32); + storage.put(key, value); + assertArrayEquals(value, (byte[]) storage.get(key)); + storage.delete(key); + assertArrayEquals(null, (byte[]) storage.get(key)); + } catch (Exception e) { + logger.error("delete error", e); + assertFalse(true); + } + } + + public static Map createBatch(int batchSize) { + HashMap result = new HashMap<>(); + while (result.size() < batchSize) { + result.put(new ByteArrayWrapper(RandomUtils.randomBytes(32)), RandomUtils.randomBytes(32)); + } + return result; + } + +} diff --git a/storage/src/test/java/org/platon/storage/datasource/WriteCacheTest.java b/storage/src/test/java/org/platon/storage/datasource/WriteCacheTest.java new file mode 100644 index 0000000..3269279 --- /dev/null +++ b/storage/src/test/java/org/platon/storage/datasource/WriteCacheTest.java @@ -0,0 +1,56 @@ +package org.platon.storage.datasource; + +import org.junit.Assert; +import org.junit.Test; +import org.platon.common.utils.ByteComparator; +import org.platon.common.wrapper.DataWord; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.inmemory.HashMapDB; + +import java.math.BigInteger; + +public class WriteCacheTest { + + public byte[] toKey(int intKey){ + return HashUtil.sha3(BigInteger.valueOf(intKey).toByteArray()); + } + + public byte[] toV(int intV) { + return DataWord.of(intV).getData(); + } + + @Test + public void test01() { + + Source dbSource = new HashMapDB<>(); + WriteCache writeCache = new WriteCache.BytesKey<>(dbSource, WriteCache.CacheType.SIMPLE); + + for (int i = 0; i < 1_000; ++i) { + writeCache.put(toKey(i), DataWord.of(i).getData()); + } + + Assert.assertTrue(ByteComparator.equals(DataWord.of(0).getData(), writeCache.getCached(toKey(0)).value())); + Assert.assertTrue(ByteComparator.equals(DataWord.of(999).getData(), writeCache.getCached(toKey(999)).value())); + + writeCache.flush(); + + + Assert.assertNull(writeCache.getCached(toKey(0))); + Assert.assertNull(writeCache.getCached(toKey(999))); + + + Assert.assertTrue(ByteComparator.equals(DataWord.of(0).getData(), writeCache.get(toKey(0)))); + Assert.assertTrue(ByteComparator.equals(DataWord.of(999).getData(), writeCache.get(toKey(999)))); + + writeCache.put(toKey(0),toV(111)); + Assert.assertTrue(ByteComparator.equals(toV(111), writeCache.getCached(toKey(0)).value())); + + writeCache.delete(toKey(0)); + + Assert.assertTrue(null == writeCache.getCached(toKey(0)) || null == writeCache.getCached(toKey(0)).value()); + writeCache.flush(); + Assert.assertNull(dbSource.get(toKey(0))); + + } + +} diff --git a/storage/src/test/java/org/platon/storage/datasource/leveldb/LevelDBSourceTest.java b/storage/src/test/java/org/platon/storage/datasource/leveldb/LevelDBSourceTest.java new file mode 100644 index 0000000..0bafd8d --- /dev/null +++ b/storage/src/test/java/org/platon/storage/datasource/leveldb/LevelDBSourceTest.java @@ -0,0 +1,86 @@ +package org.platon.storage.datasource.leveldb; + +import org.junit.*; +import org.platon.common.utils.ByteComparator; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.DbSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +@Ignore +public class LevelDBSourceTest { + + private Logger logger = LoggerFactory.getLogger("leveldbtest"); + + private DbSource dbSource; + + @Before + public void before(){ + String dir = System.getProperty("user.dir"); + dbSource = new LevelDBSource("testdb", dir); + dbSource.reset(); + dbSource.open(); + } + + @Test + public void testUpdateBatch() { + final int dataSize = 50; + Map rows = createData(dataSize); + dbSource.updateBatch(rows); + + Assert.assertEquals(dataSize, dbSource.keys().size()); + try { + dbSource.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testPutAndDelete(){ + + + byte[] key = randomBytes(32); + byte[] value = HashUtil.sha3(new byte[]{1, 2}); + dbSource.put(key, value); + Assert.assertNotNull(dbSource.get(key)); + + + byte[] res = dbSource.get(key); + Assert.assertTrue(ByteComparator.equals(value, res)); + + + dbSource.delete(key); + Assert.assertNull(dbSource.get(key)); + } + + static Map createData(int size){ + Map result = new HashMap<>(); + for (int i = 0; i < size; i++) { + result.put(randomBytes(32), randomBytes(32)); + } + return result; + } + + static byte[] randomBytes(int length) { + byte[] result = new byte[length]; + new Random().nextBytes(result); + return result; + } + + @After + public void after(){ + try{ + if (dbSource != null) { + dbSource.close(); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/storage/src/test/java/org/platon/storage/datasource/rocksdb/RocksDBSourceTest.java b/storage/src/test/java/org/platon/storage/datasource/rocksdb/RocksDBSourceTest.java new file mode 100644 index 0000000..216423a --- /dev/null +++ b/storage/src/test/java/org/platon/storage/datasource/rocksdb/RocksDBSourceTest.java @@ -0,0 +1,83 @@ +package org.platon.storage.datasource.rocksdb; + +import org.junit.*; +import org.platon.common.utils.ByteComparator; +import org.platon.crypto.HashUtil; +import org.platon.storage.datasource.DbSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +@Ignore +public class RocksDBSourceTest { + + private Logger logger = LoggerFactory.getLogger("rocksdbtest"); + + private DbSource dbSource; + + @Before + public void before(){ + String dir = System.getProperty("user.dir"); + dbSource = new RocksDBSource("testdb", dir); + dbSource.reset(); + dbSource.open(); + } + + @Test + public void testBatch() { + final int dataSize = 100; + Map rows = createData(dataSize); + dbSource.updateBatch(rows); + Assert.assertEquals(dataSize, dbSource.keys().size()); + System.out.println("testBatch done."); + } + + @Test + public void testPutAndDelete(){ + + + byte[] key = randomBytes(32); + byte[] value = HashUtil.sha3(new byte[]{1, 2, 3}); + dbSource.put(key, value); + Assert.assertNotNull(dbSource.get(key)); + + + byte[] res = dbSource.get(key); + Assert.assertTrue(ByteComparator.equals(value, res)); + + + dbSource.delete(key); + Assert.assertNull(dbSource.get(key)); + + System.out.println("testPutAndDelete done."); + } + + static Map createData(int size){ + Map result = new HashMap<>(); + for (int i = 0; i < size; i++) { + result.put(randomBytes(32), randomBytes(32)); + } + return result; + } + + static byte[] randomBytes(int length) { + byte[] result = new byte[length]; + new Random().nextBytes(result); + return result; + } + + @After + public void after(){ + try{ + if (dbSource != null) { + dbSource.close(); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/storage/src/test/java/org/platon/storage/trie/TrieTest.java b/storage/src/test/java/org/platon/storage/trie/TrieTest.java new file mode 100644 index 0000000..7b9496a --- /dev/null +++ b/storage/src/test/java/org/platon/storage/trie/TrieTest.java @@ -0,0 +1,263 @@ +package org.platon.storage.trie; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.platon.common.utils.RandomUtils; +import org.platon.storage.datasource.CachedSource; +import org.platon.storage.datasource.Source; +import org.platon.storage.datasource.WriteCache; +import org.platon.storage.datasource.inmemory.HashMapDB; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TrieTest { + + private Logger logger = LoggerFactory.getLogger("trieTest"); + + private Source dbSource; + private CachedSource.BytesKey trieCache; + private TrieImpl stateTrie; + + @Before + public void before() { + dbSource = new HashMapDB<>(); + trieCache = new WriteCache.BytesKey<>(dbSource, WriteCache.CacheType.COUNTING); + stateTrie = new TrieImpl(trieCache, true, 1024); + } + + public void reset(boolean useSPV) { + dbSource = new HashMapDB<>(); + trieCache = new WriteCache.BytesKey<>(dbSource, WriteCache.CacheType.COUNTING); + stateTrie = new TrieImpl(trieCache, useSPV, 1024); + } + + @Test + public void put() { + + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + this.initTrie(stateTrie, 100, 100, 1000, dataMap, keyList); + logger.debug("trie结构 : " + stateTrie.dumpStructure()); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + Assert.assertArrayEquals(dataMap.get(key), stateTrie.get(key)); + } + } + + @Test + public void delete() { + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + this.initTrie(stateTrie, 100, 100, 1000, dataMap, keyList); + for (int i = 99; i >= 0; i--) { + int index = (int) (Math.random() * i); + byte[] key = keyList.get(index); + stateTrie.delete(key); + keyList.remove(index); + logger.debug("第{}次:{}", i, keyList.size()); + Assert.assertNull(stateTrie.get(key)); + } + logger.debug("trie结构:" + stateTrie.dumpStructure()); + } + + @Test + public void getRootHash() { + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + this.initTrie(stateTrie, 100, 100, 1000, dataMap, keyList); + logger.debug("trie结构:" + stateTrie.dumpStructure()); + + + byte[] hash = stateTrie.getRootHash(); + + + reset(false); + stateTrie.put(keyList.get(1), dataMap.get(keyList.get(1))); + logger.debug("newTrie结构:" + stateTrie.dumpStructure()); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + if(i == 1){ + Assert.assertArrayEquals(dataMap.get(key), stateTrie.get(key)); + } + } + } + + @Test + public void spvGetAndVerify() { + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + + int dataSize = 200; + int maxKeyLen = 1000000; + int maxValLen = 500; + + for (int i = 0; i < dataSize; i++) { + + byte[] key = RandomUtils.randomBytes(RandomUtils.randomInt(maxKeyLen) + 1); + + byte[] value = RandomUtils.randomBytes(RandomUtils.randomInt(maxValLen) + 1); + dataMap.put(key, value); + keyList.add(key); + stateTrie.put(key, value); + } + + byte[] rootHash = stateTrie.getRootHash(); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + byte[] spvHash = stateTrie.bcSHA3Digest256(stateTrie.get(key)); + byte[] spvEncodeData = stateTrie.spvEncodeHashList(key); + byte[] caculedRootHash; + try { + TrieProto.NodeBase nodeBase = TrieProto.NodeBase.parseFrom(spvEncodeData); + caculedRootHash = nodeBase.getHash().toByteArray(); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Decode proto data error "); + } + Assert.assertArrayEquals(rootHash, caculedRootHash); + boolean flag = stateTrie.spvVerifyHashList(spvHash, spvEncodeData); + Assert.assertTrue(flag); + } + } + + private Trie initTrie(TrieImpl stateTrie, int dataSize, int maxKeyLen, int maxValLen, Map dataMap, List keyList) { + for (int i = 0; i < dataSize; i++) { + + byte[] key = RandomUtils.randomBytes(RandomUtils.randomInt(maxKeyLen)); + + byte[] value = RandomUtils.randomBytes(RandomUtils.randomInt(maxValLen)); + dataMap.put(key, value); + keyList.add(key); + stateTrie.put(key, value); + } + return stateTrie; + } + + + + + @Test + public void putHashMap() { + + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + + logger.debug("trie结构:" + stateTrie.dumpStructure()); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + + + + Assert.assertArrayEquals(dataMap.get(key), stateTrie.get(key)); + } + } + + @Test + public void deleteHashMap() { + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + this.initTrieHashMap(100, 100, 1000, dataMap, keyList); + for (int i = 99; i >= 0; i--) { + int index = (int) (Math.random() * i); + byte[] key = keyList.get(index); + stateTrie.delete(key); + keyList.remove(index); + logger.debug("第{}次:{} ", i, keyList.size()); + Assert.assertNull(stateTrie.get(key)); + } + logger.debug("trie结构:" + stateTrie.dumpStructure()); + } + + @Test + public void getRootHashMapHash() { + + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + this.initTrieHashMap(100, 100, 1000, dataMap, keyList); + logger.debug("trie结构:" + stateTrie.dumpStructure()); + + + byte[] hash = stateTrie.getRootHash(); + + + reset(true); + for (int i = 0; i < keyList.size(); i++) { + stateTrie.put(keyList.get(i), dataMap.get(keyList.get(i))); + } + logger.debug("newTrie结构:" + stateTrie.dumpStructure()); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + Assert.assertArrayEquals(dataMap.get(key), stateTrie.get(key)); + } + + byte[] newHash = stateTrie.getRootHash(); + + Assert.assertArrayEquals(hash, stateTrie.getRootHash()); + } + + @Test + public void spvGetAndVerifyHashMap() { + + Map dataMap = new HashMap<>(); + List keyList = new ArrayList<>(); + + int dataSize = 200; + int maxKeyLen = 1000000; + int maxValLen = 500; + TrieImpl trie = new TrieImpl(); + for (int i = 0; i < dataSize; i++) { + + byte[] key = RandomUtils.randomBytes(RandomUtils.randomInt(maxKeyLen) + 1); + + byte[] value = RandomUtils.randomBytes(RandomUtils.randomInt(maxValLen) + 1); + dataMap.put(key, value); + trie.put(key, value); + keyList.add(key); + } + + byte[] rootHash = trie.getRootHash(); + for (int i = 0; i < keyList.size(); i++) { + byte[] key = keyList.get(i); + byte[] spvHash = trie.bcSHA3Digest256(trie.get(key)); + byte[] spvEncodeData = trie.spvEncodeHashList(key); + byte[] caculedRootHash; + try { + TrieProto.NodeBase nodeBase = TrieProto.NodeBase.parseFrom(spvEncodeData); + caculedRootHash = nodeBase.getHash().toByteArray(); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + throw new RuntimeException("Decode proto data error"); + } + Assert.assertArrayEquals(rootHash, caculedRootHash); + boolean flag = trie.spvVerifyHashList(spvHash, spvEncodeData); + Assert.assertTrue(flag); + } + } + + private Trie initTrieHashMap(int dataSize, int maxKeyLen, int maxValLen, Map dataMap, List keyList) { + for (int i = 0; i < dataSize; i++) { + + byte[] key = RandomUtils.randomBytes(RandomUtils.randomInt(maxKeyLen) + 1); + + byte[] value = RandomUtils.randomBytes(RandomUtils.randomInt(maxValLen) + 2); + dataMap.put(key, value); + keyList.add(key); + stateTrie.put(key, value); + } + return stateTrie; + } +} \ No newline at end of file diff --git a/storage/src/test/resources/applicationContext.xml b/storage/src/test/resources/applicationContext.xml new file mode 100644 index 0000000..69fbf65 --- /dev/null +++ b/storage/src/test/resources/applicationContext.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storage/src/test/resources/config/system.properties b/storage/src/test/resources/config/system.properties new file mode 100644 index 0000000..23dd6de --- /dev/null +++ b/storage/src/test/resources/config/system.properties @@ -0,0 +1,2 @@ +leveldbPath=C:\\sunzone\\leveldb +rocksdbPath=C:\\sunzone\\rocksdb \ No newline at end of file diff --git a/storage/src/test/resources/logback.xml b/storage/src/test/resources/logback.xml new file mode 100644 index 0000000..e3aec56 --- /dev/null +++ b/storage/src/test/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg %n + + + DEBUG + + + + + ../logs/storage_test.log + + ../logs/storage_test_%d{yyyy-MM-dd}.log + 30 + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/build.gradle b/tests/build.gradle new file mode 100644 index 0000000..a8e28f7 --- /dev/null +++ b/tests/build.gradle @@ -0,0 +1,9 @@ +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/vm/build.gradle b/vm/build.gradle new file mode 100644 index 0000000..5505c8c --- /dev/null +++ b/vm/build.gradle @@ -0,0 +1,10 @@ + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +}