From fb0646d37bd30554740051446118e619118b514a Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 25 Feb 2022 01:12:45 +0900 Subject: [PATCH 01/89] Implement getHeader APIs and update block data structure Block Klaytn added baseFeePerGas field at block header so Block must include that field. BlockHeader class for response of header APIs is added. Updated docker-compose Klaytn v1.8.0 is not yet released , so to test and development new feature, we need to update docker-compose.yml. --- .circleci/images/docker-compose.yml | 4 +- .../klaytn/caver/methods/response/Block.java | 19 +- .../caver/methods/response/BlockHeader.java | 265 ++++++++++++++++++ .../main/java/com/klaytn/caver/rpc/Klay.java | 66 +++++ .../com/klaytn/caver/common/rpc/RpcTest.java | 57 +++- 5 files changed, 401 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java diff --git a/.circleci/images/docker-compose.yml b/.circleci/images/docker-compose.yml index 6b5c9bca9..145f8e423 100755 --- a/.circleci/images/docker-compose.yml +++ b/.circleci/images/docker-compose.yml @@ -16,11 +16,11 @@ services: - | git clone https://github.com/klaytn/klaytn.git cd klaytn - git checkout master + git checkout dev make kcn export PATH=$PATH:`pwd`/build/bin mkdir -p /klaytn - echo '{"config":{"chainId":2019,"istanbul":{"epoch":604800,"policy":2,"sub":13},"unitPrice":25000000000,"deriveShaImpl":2,"governance":{"governingNode":"0x73718c4980728857f3aa5148e9d1b471efa3a7dd","governanceMode":"single","reward":{"mintingAmount":9600000000000000000,"ratio":"34/54/12","useGiniCoeff":false,"deferredTxFee":true,"stakingUpdateInterval":86400,"proposerUpdateInterval":3600,"minimumStake":5000000}}},"timestamp":"0x5cef3d41","extraData":"0x0000000000000000000000000000000000000000000000000000000000000000f85ad59473718c4980728857f3aa5148e9d1b471efa3a7ddb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","governanceData":null,"blockScore":"0x1","alloc":{"0000000000000000000000000000000000000400":{"code":"0x6080604052600436106102215763ffffffff60e060020a60003504166315575d5a8114610226578063160370b8146102725780631865c57d1461037c57806321ac4ad4146103ec57806327e1f7df14610412578063298b3c611461043357806332b91e8514610460578063394a144a1461048b5780633f0628b1146104ac578063407091eb146104d057806341b6945c1461056c5780634a8c1fb4146105815780634b6a94cc146105aa5780634c5d435c146106345780634f97638f1461065857806350a5bb691461066d578063579740db1461068257806358d65880146106a35780636abd623d146106ca57806370480275146106fb578063715b208b1461071c57806376674c54146107ca578063778f39cb146107df5780637894c366146107f4578063791b51231461081857806382d67e5a14610839578063832a2aad146108d5578063863f5c0a146108f657806387cd9feb146109175780638e6f6b771461092c5780639258d76814610941578063934d1fa414610965578063afaaf3301461097a578063b50060e41461099b578063b5067706146109b0578063b7563930146109d1578063b858dd95146109e6578063c47afb3a146109fb578063c7e9de7514610a13578063cc11efc014610a37578063cec9246614610a64578063d267eda514610a79578063da34a0bd14610a8e578063de5bbfbc14610af3578063e748357b14610b08578063feb15ca114610b20578063ffa1ad7414610b35575b600080fd5b34801561023257600080fd5b50610247600160a060020a0360043516610b4a565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b34801561027e57600080fd5b50610287610ca0565b60408051600160a060020a0380851660608301528316608082015260a080825287519082015286519091829160208084019284019160c08501918b8101910280838360005b838110156102e45781810151838201526020016102cc565b50505050905001848103835288818151815260200191508051906020019060200280838360005b8381101561032357818101518382015260200161030b565b50505050905001848103825287818151815260200191508051906020019060200280838360005b8381101561036257818101518382015260200161034a565b505050509050019850505050505050505060405180910390f35b34801561038857600080fd5b50610391610deb565b6040518080602001838152602001828103825284818151815260200191508051906020019060200280838360005b838110156103d75781810151838201526020016103bf565b50505050905001935050505060405180910390f35b3480156103f857600080fd5b50610410600160a060020a0360043516602435610e57565b005b34801561041e57600080fd5b50610410600160a060020a0360043516611038565b34801561043f57600080fd5b50610410600160a060020a03600435811690602435811690604435166112c9565b34801561046c57600080fd5b50610475611720565b6040805160ff9092168252519081900360200190f35b34801561049757600080fd5b50610410600160a060020a0360043516611725565b3480156104b857600080fd5b5061041060ff600435166024356044356064356117f6565b3480156104dc57600080fd5b506104f460ff60043516602435604435606435611e88565b60408051858152908101839052602081016060820183600481111561051557fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360005b8381101561055557818101518382015260200161053d565b505050509050019550505050505060405180910390f35b34801561057857600080fd5b50610475611f35565b34801561058d57600080fd5b50610596611f3a565b604080519115158252519081900360200190f35b3480156105b657600080fd5b506105bf611f43565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105f95781810151838201526020016105e1565b50505050905090810190601f1680156106265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561064057600080fd5b50610410600160a060020a0360043516602435611f7a565b34801561066457600080fd5b5061041061216f565b34801561067957600080fd5b50610596612274565b34801561068e57600080fd5b50610410600160a060020a0360043516612282565b3480156106af57600080fd5b506106b8612658565b60408051918252519081900360200190f35b3480156106d657600080fd5b506106df61265d565b60408051600160a060020a039092168252519081900360200190f35b34801561070757600080fd5b50610410600160a060020a036004351661266c565b34801561072857600080fd5b5061073161284b565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561077557818101518382015260200161075d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156107b457818101518382015260200161079c565b5050505090500194505050505060405180910390f35b3480156107d657600080fd5b50610475612b3a565b3480156107eb57600080fd5b506106b8612b3f565b34801561080057600080fd5b50610410602460048035828101929101359035612b44565b34801561082457600080fd5b50610410600160a060020a0360043516612ef1565b34801561084557600080fd5b506108516004356130f6565b6040518088600a81111561086157fe5b60ff16815260208101889052604081018790526060810186905260a08101849052608081019060c00183600481111561089657fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360008381101561036257818101518382015260200161034a565b3480156108e157600080fd5b50610410600160a060020a03600435166131b4565b34801561090257600080fd5b50610410600160a060020a03600435166133bd565b34801561092357600080fd5b5061041061358b565b34801561093857600080fd5b506106b8613649565b34801561094d57600080fd5b50610410600160a060020a0360043516602435613650565b34801561097157600080fd5b506106b86137e8565b34801561098657600080fd5b50610410600160a060020a03600435166137ef565b3480156109a757600080fd5b5061047561388e565b3480156109bc57600080fd5b50610410600160a060020a0360043516613893565b3480156109dd57600080fd5b506106b8613a49565b3480156109f257600080fd5b506106df613a4f565b348015610a0757600080fd5b50610410600435613a5e565b348015610a1f57600080fd5b50610410600160a060020a0360043516602435613bb1565b348015610a4357600080fd5b50610410600160a060020a0360043581169060243581169060443516613da6565b348015610a7057600080fd5b50610410614265565b348015610a8557600080fd5b506106df6145b5565b348015610a9a57600080fd5b50610aa36145c4565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610adf578181015183820152602001610ac7565b505050509050019250505060405180910390f35b348015610aff57600080fd5b5061047561461e565b348015610b1457600080fd5b50610410600435614623565b348015610b2c57600080fd5b506104106147a6565b348015610b4157600080fd5b506106b861461e565b600080808084600160a060020a0381161515610b9e576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600160a060020a038616600081815260086020526040902054600980549194509084908110610bc957fe5b600091825260209091200154600160a060020a031614610c21576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009805483908110610c2f57fe5b600091825260209091200154600a8054600160a060020a039092169184908110610c5557fe5b600091825260209091200154600b8054600160a060020a039092169185908110610c7b57fe5b6000918252602090912001549196509450600160a060020a0316925050509193909250565b60055460065460098054604080516020808402820181019092528281526060958695869560009586959194600a94600b94600160a060020a03918216949116928791830182828015610d1b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610cfd575b5050505050945083805480602002602001604051908101604052809291908181526020018280548015610d7757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610d59575b5050505050935082805480602002602001604051908101604052809291908181526020018280548015610dd357602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610db5575b50505050509250945094509450945094509091929394565b606060008060015481805480602002602001604051908101604052809291908181526020018280548015610e4857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610e2a575b50505050509150915091509091565b600082600160a060020a0381161515610ea8576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515610eff576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610f3e57600080fd5b505af1158015610f52573d6000803e3d6000fd5b505050506040513d6020811015610f6857600080fd5b505114610fbf576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b610fd66006600160a060020a038716866000614af1565b9250610fef6006600160a060020a038716866000614bc4565b610ff8836151f5565b80156110235750600160008481526003602052604090206006015460ff16600481111561102157fe5b145b156110315761103183615210565b5050505050565b600080333014611080576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff1615156110e2576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6000546110f690600163ffffffff61591416565b6001546032821115801561110a5750818111155b801561111557508015155b801561112057508115155b1515611164576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b60008054600160a060020a0388168252600260205260408220805460ff19169055955093505b6001850384101561123d5785600160a060020a03166000858154811015156111ae57fe5b600091825260209091200154600160a060020a03161415611232576000805460001987019081106111db57fe5b60009182526020822001548154600160a060020a039091169190869081106111ff57fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a0316021790555061123d565b60019093019261118a565b60008054600019870190811061124f57fe5b600091825260208220018054600160a060020a03191690555461127990600163ffffffff61591416565b611284600082615a01565b5061128d61216f565b604051600160a060020a038716907f1af6bd3d85a56e7c4a0700756fd2ca3b3b65c266e56c77652c5a346bc256522090600090a2505050505050565b6000333014611310576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600954600010156113ae57600160a060020a03841660008181526008602052604090205460098054909190811061134357fe5b600091825260209091200154600160a060020a031614156113ae576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b83600160a060020a031683600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156113f657600080fd5b505af115801561140a573d6000803e3d6000fd5b505050506040513d602081101561142057600080fd5b5051600160a060020a03161461146e576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b81600160a060020a031683600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156114b657600080fd5b505af11580156114ca573d6000803e3d6000fd5b505050506040513d60208110156114e057600080fd5b5051600160a060020a031614611540576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b82600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561157e57600080fd5b505af1158015611592573d6000803e3d6000fd5b505050506040513d60208110156115a857600080fd5b50511515600114611603576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b5060098054600160a060020a03808616600081815260086020908152604080832086905560018087019097557f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af86018054600160a060020a03199081168617909155600a8054808a019091557fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80180548b88169083168117909155600b8054998a0181559094527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99097018054958916959097168517909655855192835282015280840191909152915190917fe01726557c1ea9f7286dca4bba890e96fea9041689db298806306cafa74c9e91919081900360600190a150505050565b600281565b3360008181526002602052604081205490919060ff16151561177f576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b611796600a600160a060020a038516600080614af1565b91506117af600a600160a060020a038516600080614bc4565b6117b8826151f5565b80156117e35750600160008381526003602052604090206006015460ff1660048111156117e157fe5b145b156117f1576117f182615210565b505050565b33600081815260026020526040812054909182918291829160ff161515611855576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b61186189898989614af1565b60008181526003602052604090206005015490955015156118cc576040805160e560020a62461bcd02815260206004820152601060248201527f496e76616c696420726571756573742e00000000000000000000000000000000604482015290519081900360640190fd5b600160008681526003602052604090206006015460ff1660048111156118ee57fe5b14611943576040805160e560020a62461bcd02815260206004820152601f60248201527f4d757374206265206174206e6f742d636f6e6669726d65642073746174652e00604482015290519081900360640190fd5b60008581526003602052604081206004015490945092508391505b82821015611e2657600085815260036020526040902060040180548390811061198357fe5b600091825260209091200154600160a060020a0316331415611e1b57600085815260036020526040902060050154600194504262093a809091011015611b2f5760008581526003602052604090206005015442621275009091011015611a3e576119ec8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611a266004830182615a25565b5060006005820155600601805460ff19169055611a5b565b6000858152600360205260409020600601805460ff191660041790555b600085815260036020526040908190209051339187917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918d918d918d918d91600401908086600a811115611aac57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611b1857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611afa575b5050965050505050505060405180910390a3611e16565b60001983018214611bc057600085815260036020526040902060040180546000198501908110611b5b57fe5b6000918252602080832090910154878352600390915260409091206004018054600160a060020a039092169184908110611b9157fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055505b600085815260036020526040902060040180546000198501908110611be157fe5b600091825260208083209091018054600160a060020a0319169055868252600390526040902060040154611c1c90600163ffffffff61591416565b6000868152600360205260409020611c379060040182615a01565b5060008581526003602081905260409182902080546001820154600283015493830154945133958b957f9c174b2536ba49e3478ca649dac74d9e9f71f70adf70f193e780eabbfcdc367c9560ff9095169490926004909101908086600a811115611c9d57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611d0957602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611ceb575b5050965050505050505060405180910390a36000858152600360205260409020600401541515611e1657611d3c8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611d766004830182615a25565b506000600582018190556006909101805460ff19169055858152600360208190526040918290208054600182015460028301549290930154935133948a947fbfda049a0206fd9c90ed4a3170f5bfaad83c323a16835dd68fea92faa247c2cd9460ff9094169390929091908085600a811115611dee57fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a35b611e26565b60019091019061195e565b831515611e7d576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220686173206e6f74207265717565737465642e000000604482015290519081900360640190fd5b505050505050505050565b600060606000806000611e9d89898989614af1565b600081815260036020908152604091829020600581015460068201546004909201805485518186028101860190965280865295965086959094919360ff90931692859190830182828015611f1a57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611efc575b50505050509250945094509450945050945094509450949050565b600481565b600c5460ff1681565b60408051808201909152600b81527f41646472657373426f6f6b000000000000000000000000000000000000000000602082015281565b600080333014611fc2576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561200157600080fd5b505af1158015612015573d6000803e3d6000fd5b505050506040513d602081101561202b57600080fd5b505114612082576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060068054600160a060020a03848116600160a060020a031983161790925516600081156121185781600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156120e957600080fd5b505af11580156120fd573d6000803e3d6000fd5b505050506040513d602081101561211357600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517ffdccdf242038c2d09605009fbb95e03f75cdbd106d0a9e52a1670be9553c88489181900360800190a150505050565b6000803330146121b7576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b505060045460005b8181101561223b57600360006004838154811015156121da57fe5b600091825260208083209091015483528201929092526040018120805460ff19168155600181018290556002810182905560038101829055906122206004830182615a25565b5060006005820155600601805460ff191690556001016121bf565b61224760046000615a25565b6040517f907527d30089abd16e30f06ddbbbc18480505176262f19bc16c1fbf9262f9c6b90600090a15050565b600c54610100900460ff1681565b60003330146122c9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b50600160a060020a03811660008181526008602052604090205460098054919291839081106122f457fe5b600091825260209091200154600160a060020a03161461234c576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009546001106123a6576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b6009546000190181101561251e576009805460001981019081106123c657fe5b60009182526020909120015460098054600160a060020a0390921691839081106123ec57fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600a805490916000190190811061242b57fe5b600091825260209091200154600a8054600160a060020a03909216918390811061245157fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600b805490916000190190811061249057fe5b600091825260209091200154600b8054600160a060020a0390921691839081106124b657fe5b600091825260208220018054600160a060020a031916600160a060020a039390931692909217909155600980548392600892909160001981019081106124f857fe5b6000918252602080832090910154600160a060020a031683528201929092526040019020555b600160a060020a03821660009081526008602052604081205560098054600019810190811061254957fe5b60009182526020909120018054600160a060020a031916905560095461257690600163ffffffff61591416565b612581600982615a01565b50600a8054600019810190811061259457fe5b60009182526020909120018054600160a060020a0319169055600a546125c190600163ffffffff61591416565b6125cc600a82615a01565b50600b805460001981019081106125df57fe5b60009182526020909120018054600160a060020a0319169055600b5461260c90600163ffffffff61591416565b612617600b82615a01565b5060408051600160a060020a038416815290517fa30079721e55931e89e7cdb421712ad0fcc817e7cac8fe954aa7ed0d46b9c42d9181900360200190a15050565b603281565b600754600160a060020a031681565b3330146126b1576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038116600090815260026020526040902054819060ff1615612724576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b60005461273890600163ffffffff6159e816565b6001546032821115801561274c5750818111155b801561275757508015155b801561276257508115155b15156127a6576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600160a060020a0384166000818152600260205260408120805460ff19166001908117909155815490810182559080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563018054600160a060020a031916909117905561281161216f565b604051600160a060020a038516907fad6de4452a631e641cb59902236607946ce9272b9b981f2f80e8d129cb9084ba90600090a250505050565b600c54606090819081908190600090819060ff161515612884576040805160008082526020820190815281830190925294509250612b2f565b6009805490506003026002016040519080825280602002602001820160405280156128b9578160200160208202803883390190505b5093506009805490506003026002016040519080825280602002602001820160405280156128f1578160200160208202803883390190505b506009549093509150600090505b81811015612a71576000848260030281518110151561291a57fe5b60ff909216602092830290910190910152600980548290811061293957fe5b6000918252602090912001548351600160a060020a039091169084906003840290811061296257fe5b600160a060020a039092166020928302909101909101528351600190859060038402830190811061298f57fe5b60ff909216602092830290910190910152600a8054829081106129ae57fe5b6000918252602090912001548351600160a060020a03909116908490600384026001019081106129da57fe5b600160a060020a0390921660209283029091019091015283516002908590600384028301908110612a0757fe5b60ff909216602092830290910190910152600b805482908110612a2657fe5b6000918252602090912001548351600160a060020a0390911690849060026003850201908110612a5257fe5b600160a060020a039092166020928302909101909101526001016128ff565b60038483600302815181101515612a8457fe5b60ff9092166020928302909101909101526005548351600160a060020a0390911690849060038502908110612ab557fe5b600160a060020a039092166020928302909101909101528351600490859060016003860201908110612ae357fe5b60ff9092166020928302909101909101526006548351600160a060020a0390911690849060038502600101908110612b1757fe5b600160a060020a039092166020928302909101909101525b509194909350915050565b600081565b606481565b600080838360328211801590612b5a5750818111155b8015612b6557508015155b8015612b7057508115155b1515612bb4576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b7388bb3838aa0a140acb73eeb3d4b25a8d3afd58d43314612c1f576040805160e560020a62461bcd02815260206004820152600f60248201527f496e76616c69642073656e6465722e0000000000000000000000000000000000604482015290519081900360640190fd5b600c54610100900460ff1615612c7f576040805160e560020a62461bcd02815260206004820152601460248201527f416c726561647920636f6e73747275637465642e000000000000000000000000604482015290519081900360640190fd5b600c805460ff19169055859350600092505b83831015612da65760026000888886818110612ca957fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff16158015612d015750868684818110612ce357fe5b90506020020135600160a060020a0316600160a060020a0316600014155b1515612d57576040805160e560020a62461bcd02815260206004820152601e60248201527f41646472657373206973206e756c6c206f72206e6f7420756e697175652e0000604482015290519081900360640190fd5b600160026000898987818110612d6957fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff191691151591909117905560019290920191612c91565b612db260008888615a46565b506001859055600c805461ff001916610100179055604080518082018252600b81527f41646472657373426f6f6b00000000000000000000000000000000000000000060208083019182528351938401899052606080855283519085015282517fc5caa942b8f8ea45a2e094d941dbba0ef9c0307f34c81ce78e71bfb128d6b25a946000938b939192839283019160808401918083838a5b83811015612e62578181015183820152602001612e4a565b50505050905090810190601f168015612e8f5780820380516001836020036101000a031916815260200191505b508381038252858181548152602001915080548015612ed757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311612eb9575b50509550505050505060405180910390a150505050505050565b600160a060020a038116600090815260026020526040812054829060ff161515612f53576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515612faa576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b83600160a060020a0381161515612ff9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b60005461300d90600163ffffffff61591416565b600154603282111580156130215750818111155b801561302c57508015155b801561303757508115155b151561307b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6130926002600160a060020a038916600080614af1565b95506130ab6002600160a060020a038916600080614bc4565b6130b4866151f5565b80156130df5750600160008781526003602052604090206006015460ff1660048111156130dd57fe5b145b156130ed576130ed86615210565b50505050505050565b60008181526003602081815260408084208054600182015460028301549583015460058401546006850154600490950180548751818a0281018a019098528088528a998a998a996060998b998a9960ff91821699909895979096909594919092169291859183018282801561319457602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311613176575b505050505092509650965096509650965096509650919395979092949650565b60008080808085600160a060020a0381161515613209576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600a546000965086955093508492505b8383101561326457600a80543391908590811061323257fe5b600091825260209091200154600160a060020a031614156132595760019550829450613264565b600190920191613219565b8515156132bb576040805160e560020a62461bcd02815260206004820152601e60248201527f4d73672e73656e646572206973206e6f7420434e20636f6e74726163742e0000604482015290519081900360640190fd5b600b8054869081106132c957fe5b600091825260209091200154600b8054600160a060020a0390921693508891879081106132f257fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055507faa5c92ffd739bc0b8b117b671e7d713917ddb1440b436263a3ea106d70c6f05f60098681548110151561335057fe5b600091825260209091200154600b8054600160a060020a03909216918591908990811061337957fe5b6000918252602091829020015460408051600160a060020a03958616815293851692840192909252929092168183015290519081900360600190a150505050505050565b3360008181526002602052604081205490919060ff161515613417576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff161561348a576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b83600160a060020a03811615156134d9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b6000546134ed90600163ffffffff6159e816565b600154603282111580156135015750818111155b801561350c57508015155b801561351757508115155b151561355b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6135726001600160a060020a038916600080614af1565b95506130ab6001600160a060020a038916600080614bc4565b3360008181526002602052604081205490919060ff1615156135e5576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6135f3600460008080614af1565b9150613603600460008080614bc4565b61360c826151f5565b80156136375750600160008381526003602052604090206006015460ff16600481111561363557fe5b145b156136455761364582615210565b5050565b62093a8081565b600082600160a060020a03811615156136a1576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff1615156136f8576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561373757600080fd5b505af115801561374b573d6000803e3d6000fd5b505050506040513d602081101561376157600080fd5b5051146137b8576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b6137cf6007600160a060020a038716866000614af1565b9250610fef6007600160a060020a038716866000614bc4565b6212750081565b333014613834576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b60078054600160a060020a031916600160a060020a03838116919091179182905560408051929091168252517f508aacd44cfe23a34a8c2643ab08c3410cf5505632cfce58dcfa0efa2fd2ff37916020908290030190a150565b600381565b60008082600160a060020a03811615156138e5576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff16151561393c576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a03851660008181526008602052604090205460098054919650908690811061396757fe5b600091825260209091200154600160a060020a0316146139bf576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b600954600110613a19576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b613a306009600160a060020a038716600080614af1565b9250610fef6009600160a060020a038716600080614bc4565b60015481565b600654600160a060020a031681565b333014613aa3576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b6000548160328211801590613ab85750818111155b8015613ac357508015155b8015613ace57508115155b1515613b12576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154831415613b6c576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b6001839055613b7961216f565b6040805184815290517f8951393946c27b45080aad111464c16c70f3d5e7d24b114a627334441961bf5f9181900360200190a1505050565b600080333014613bf9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505050506040513d6020811015613c6257600080fd5b505114613cb9576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060058054600160a060020a03848116600160a060020a03198316179092551660008115613d4f5781600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613d2057600080fd5b505af1158015613d34573d6000803e3d6000fd5b505050506040513d6020811015613d4a57600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517fd531725ac89042f190fd73adfdeff435e07500f1a92b4b87743f1bcf91acb3a79181900360800190a150505050565b600083600160a060020a0381161515613df7576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e46576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e95576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515613eec576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60095460001015613f8a57600160a060020a038816600081815260086020526040902054600980549091908110613f1f57fe5b600091825260209091200154600160a060020a03161415613f8a576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b87600160a060020a031687600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613fd257600080fd5b505af1158015613fe6573d6000803e3d6000fd5b505050506040513d6020811015613ffc57600080fd5b5051600160a060020a03161461404a576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b85600160a060020a031687600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561409257600080fd5b505af11580156140a6573d6000803e3d6000fd5b505050506040513d60208110156140bc57600080fd5b5051600160a060020a03161461411c576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b86600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561415a57600080fd5b505af115801561416e573d6000803e3d6000fd5b505050506040513d602081101561418457600080fd5b505115156001146141df576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b6141fb6008600160a060020a03808b16908a8116908a16614af1565b94506142196008600160a060020a03808b16908a8116908a16614bc4565b614222856151f5565b801561424d5750600160008681526003602052604090206006015460ff16600481111561424b57fe5b145b1561425b5761425b85615210565b5050505050505050565b3330146142aa576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600c5460ff1615614305576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b600054151561435e576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156143c0576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614422576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600954151561447b576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a54600954146144fc576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a541461457d576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b600c805460ff191660011790556040517f29d89931226d613bf878a0be8c635eaf2049121c8c68d5ad80a78f0ac9005d4b90600090a1565b600554600160a060020a031681565b6060600480548060200260200160405190810160405280929190818152602001828054801561461357602002820191906000526020600020905b815481526001909101906020018083116145fe575b505050505090505b90565b600181565b3360008181526002602052604081205490919060ff16151561467d576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60005483603282118015906146925750818111155b801561469d57508015155b80156146a857508115155b15156146ec576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154851415614746576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b614754600386600080614af1565b9350614764600386600080614bc4565b61476d846151f5565b80156147985750600160008581526003602052604090206006015460ff16600481111561479657fe5b145b156110315761103184615210565b3360008181526002602052604081205490919060ff161515614800576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600c5460ff161561485b576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b60005415156148b4576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a03161515614916576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614978576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b60095415156149d1576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a5460095414614a52576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a5414614ad3576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b614ae1600560008080614af1565b9150613603600560008080614bc4565b6000848484846040516020018085600a811115614b0a57fe5b60ff167f010000000000000000000000000000000000000000000000000000000000000002815260018101949094525060218301919091526041808301919091526040805180840390920182526061909201918290528051909250819060208401908083835b60208310614b8f5780518252601f199092019160209182019101614b70565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912098975050505050505050565b6000806000614bd587878787614af1565b60008181526003602052604090206005015490935015614f5e5760008381526003602052604090206005015442621275009091011015614c6a57614c188361592b565b60008381526003602081905260408220805460ff19168155600181018390556002810183905590810182905590614c526004830182615a25565b5060006005820155600601805460ff19169055614f5e565b6000838152600360205260409020600501544262093a809091011015614da257600460008481526003602052604090206006015460ff166004811115614cac57fe5b14614cce576000838152600360205260409020600601805460ff191660041790555b600083815260036020526040908190209051339185917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918b918b918b918b91600401908086600a811115614d1f57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614d8b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614d6d575b5050965050505050505060405180910390a3614f5e565b6000838152600360205260409020600501544210614f5e575050600081815260036020526040812060040154905b81811015614e69576000838152600360205260409020600401805482908110614df557fe5b600091825260209091200154600160a060020a0316331415614e61576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220616c7265616479207265717565737465642e000000604482015290519081900360640190fd5b600101614dd0565b600083815260036020908152604080832060040180546001810182558185529284209092018054600160a060020a03191633908117909155928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a811115614edf57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614f4b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614f2d575b5050965050505050505060405180910390a35b60008381526003602052604090206005015415156130ed57600454606411614fe357600487600a811115614f8e57fe5b14614fe3576040805160e560020a62461bcd02815260206004820152601560248201527f52657175657374206c6973742069732066756c6c2e0000000000000000000000604482015290519081900360640190fd5b60e06040519081016040528088600a811115614ffb57fe5b81526020808201899052604080830189905260608301889052805160008082529281019091526080909201919050815242602082015260400160019052600084815260036020526040902081518154829060ff1916600183600a81111561505e57fe5b021790555060208281015160018301556040830151600283015560608301516003830155608083015180516150999260048501920190615aa9565b5060a0820151600582015560c082015160068201805460ff191660018360048111156150c157fe5b0217905550505060008381526003602090815260408083206004908101805460018181018355828752948620018054600160a060020a0319163390811790915582549485019092557f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909301879055928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a81111561516e57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c00190849080156151da57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116151bc575b5050965050505050505060405180910390a350505050505050565b60015460009182526003602052604090912060040154101590565b600061521a615afe565b600083815260036020526040808220815160e0810190925280549294509091829060ff16600a81111561524957fe5b600a81111561525457fe5b8152600182015460208083019190915260028301546040808401919091526003840154606084015260048401805482518185028101850190935280835260809094019391929091908301828280156152d557602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116152b7575b505050918352505060058201546020820152600682015460409091019060ff16600481111561530057fe5b600481111561530b57fe5b905250905060018151600a81111561531f57fe5b1415615389576020810151604080517f70480275000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916370480275916024808301926000929190829003018183875af192505050915061579a565b60028151600a81111561539857fe5b1415615402576020810151604080517f27e1f7df000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916327e1f7df916024808301926000929190829003018183875af192505050915061579a565b60038151600a81111561541157fe5b1415615473576020810151604080517fc47afb3a000000000000000000000000000000000000000000000000000000008152600481019290925251309163c47afb3a916024808301926000929190829003018183875af192505050915061579a565b60048151600a81111561548257fe5b14156154c55730600160a060020a0316634f97638f6040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60058151600a8111156154d457fe5b14156155175730600160a060020a031663cec924666040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60068151600a81111561552657fe5b141561559957602081015160408083015181517fc7e9de75000000000000000000000000000000000000000000000000000000008152600160a060020a039093166004840152602483015251309163c7e9de75916044808301926000929190829003018183875af192505050915061579a565b60078151600a8111156155a857fe5b141561561b57602081015160408083015181517f4c5d435c000000000000000000000000000000000000000000000000000000008152600160a060020a0390931660048401526024830152513091634c5d435c916044808301926000929190829003018183875af192505050915061579a565b60088151600a81111561562a57fe5b14156156ad576020810151604080830151606084015182517f298b3c61000000000000000000000000000000000000000000000000000000008152600160a060020a0394851660048201529184166024830152909216604483015251309163298b3c61916064808301926000929190829003018183875af192505050915061579a565b60098151600a8111156156bc57fe5b1415615726576020810151604080517f579740db000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163579740db916024808301926000929190829003018183875af192505050915061579a565b600a8151600a81111561573557fe5b141561579a576020810151604080517fafaaf330000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163afaaf330916024808301926000929190829003018183875af194505050505b6157a38361592b565b811561585d57600083815260036020526040902060050154156157dd576000838152600360205260409020600601805460ff191660021790555b33600160a060020a031683600019167fc55c9229184beabeee72b6970a96691b4200919e47579cc4b9be50a1bec7ef1183600001518460200151856040015186606001516040518085600a81111561583157fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a36117f1565b60008381526003602052604090206005015415615894576000838152600360208190526040909120600601805460ff191690911790555b33600160a060020a031683600019167ff151a3ee41626c2511372320f09f7957af81c8c1cde8cdf3f05a5979626eaaf383600001518460200151856040015186606001516040518085600a8111156158e857fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a3505050565b6000808383111561592457600080fd5b5050900390565b60045460005b818110156117f157600480548290811061594757fe5b6000918252602090912001548314156159e0576000198201811461599e5760048054600019840190811061597757fe5b906000526020600020015460048281548110151561599157fe5b6000918252602090912001555b6004805460001984019081106159b057fe5b60009182526020822001556004546159cf90600163ffffffff61591416565b6159da600482615a01565b506117f1565b600101615931565b6000828201838110156159fa57600080fd5b9392505050565b8154818355818111156117f1576000838152602090206117f1918101908301615b3b565b5080546000825590600052602060002090810190615a439190615b3b565b50565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a99578154600160a060020a031916600160a060020a03843516178255602090920191600190910190615a66565b50615aa5929150615b55565b5090565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a995782518254600160a060020a031916600160a060020a03909116178255602090920191600190910190615ac9565b6040805160e081019091528060008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b61461b91905b80821115615aa55760008155600101615b41565b61461b91905b80821115615aa5578054600160a060020a0319168155600101615b5b560041646472657373206973206e6f742061646d696e2e0000000000000000000000496e76616c696420434e206e6f64652049442e0000000000000000000000000041646472657373206973206e756c6c2e000000000000000000000000000000004e6f742061206d756c74697369672d7472616e73616374696f6e2e0000000000496e76616c696420726571756972656d656e742e000000000000000000000000a165627a7a7230582007320485103bb418db53fd7eb24f69d0f455d28d1b5b146eb3e7fa6237cb69f10029","balance":"0x0"},"73718c4980728857f3aa5148e9d1b471efa3a7dd":{"balance":"0x446c3b15f9926687d2c40534fdb564000000000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}' > /klaytn/genesis.json + echo '{"config":{"chainId":2019,"istanbulCompatibleBlock":0,"londonCompatibleBlock":0,"ethTxTypeCompatibleBlock":0,"istanbul":{"epoch":604800,"policy":2,"sub":13},"unitPrice":25000000000,"deriveShaImpl":2,"governance":{"governingNode":"0x73718c4980728857f3aa5148e9d1b471efa3a7dd","governanceMode":"single","reward":{"mintingAmount":9600000000000000000,"ratio":"34/54/12","useGiniCoeff":false,"deferredTxFee":true,"stakingUpdateInterval":86400,"proposerUpdateInterval":3600,"minimumStake":5000000}}},"timestamp":"0x5cef3d41","extraData":"0x0000000000000000000000000000000000000000000000000000000000000000f85ad59473718c4980728857f3aa5148e9d1b471efa3a7ddb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","governanceData":null,"blockScore":"0x1","alloc":{"0000000000000000000000000000000000000400":{"code":"0x6080604052600436106102215763ffffffff60e060020a60003504166315575d5a8114610226578063160370b8146102725780631865c57d1461037c57806321ac4ad4146103ec57806327e1f7df14610412578063298b3c611461043357806332b91e8514610460578063394a144a1461048b5780633f0628b1146104ac578063407091eb146104d057806341b6945c1461056c5780634a8c1fb4146105815780634b6a94cc146105aa5780634c5d435c146106345780634f97638f1461065857806350a5bb691461066d578063579740db1461068257806358d65880146106a35780636abd623d146106ca57806370480275146106fb578063715b208b1461071c57806376674c54146107ca578063778f39cb146107df5780637894c366146107f4578063791b51231461081857806382d67e5a14610839578063832a2aad146108d5578063863f5c0a146108f657806387cd9feb146109175780638e6f6b771461092c5780639258d76814610941578063934d1fa414610965578063afaaf3301461097a578063b50060e41461099b578063b5067706146109b0578063b7563930146109d1578063b858dd95146109e6578063c47afb3a146109fb578063c7e9de7514610a13578063cc11efc014610a37578063cec9246614610a64578063d267eda514610a79578063da34a0bd14610a8e578063de5bbfbc14610af3578063e748357b14610b08578063feb15ca114610b20578063ffa1ad7414610b35575b600080fd5b34801561023257600080fd5b50610247600160a060020a0360043516610b4a565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b34801561027e57600080fd5b50610287610ca0565b60408051600160a060020a0380851660608301528316608082015260a080825287519082015286519091829160208084019284019160c08501918b8101910280838360005b838110156102e45781810151838201526020016102cc565b50505050905001848103835288818151815260200191508051906020019060200280838360005b8381101561032357818101518382015260200161030b565b50505050905001848103825287818151815260200191508051906020019060200280838360005b8381101561036257818101518382015260200161034a565b505050509050019850505050505050505060405180910390f35b34801561038857600080fd5b50610391610deb565b6040518080602001838152602001828103825284818151815260200191508051906020019060200280838360005b838110156103d75781810151838201526020016103bf565b50505050905001935050505060405180910390f35b3480156103f857600080fd5b50610410600160a060020a0360043516602435610e57565b005b34801561041e57600080fd5b50610410600160a060020a0360043516611038565b34801561043f57600080fd5b50610410600160a060020a03600435811690602435811690604435166112c9565b34801561046c57600080fd5b50610475611720565b6040805160ff9092168252519081900360200190f35b34801561049757600080fd5b50610410600160a060020a0360043516611725565b3480156104b857600080fd5b5061041060ff600435166024356044356064356117f6565b3480156104dc57600080fd5b506104f460ff60043516602435604435606435611e88565b60408051858152908101839052602081016060820183600481111561051557fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360005b8381101561055557818101518382015260200161053d565b505050509050019550505050505060405180910390f35b34801561057857600080fd5b50610475611f35565b34801561058d57600080fd5b50610596611f3a565b604080519115158252519081900360200190f35b3480156105b657600080fd5b506105bf611f43565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105f95781810151838201526020016105e1565b50505050905090810190601f1680156106265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561064057600080fd5b50610410600160a060020a0360043516602435611f7a565b34801561066457600080fd5b5061041061216f565b34801561067957600080fd5b50610596612274565b34801561068e57600080fd5b50610410600160a060020a0360043516612282565b3480156106af57600080fd5b506106b8612658565b60408051918252519081900360200190f35b3480156106d657600080fd5b506106df61265d565b60408051600160a060020a039092168252519081900360200190f35b34801561070757600080fd5b50610410600160a060020a036004351661266c565b34801561072857600080fd5b5061073161284b565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561077557818101518382015260200161075d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156107b457818101518382015260200161079c565b5050505090500194505050505060405180910390f35b3480156107d657600080fd5b50610475612b3a565b3480156107eb57600080fd5b506106b8612b3f565b34801561080057600080fd5b50610410602460048035828101929101359035612b44565b34801561082457600080fd5b50610410600160a060020a0360043516612ef1565b34801561084557600080fd5b506108516004356130f6565b6040518088600a81111561086157fe5b60ff16815260208101889052604081018790526060810186905260a08101849052608081019060c00183600481111561089657fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360008381101561036257818101518382015260200161034a565b3480156108e157600080fd5b50610410600160a060020a03600435166131b4565b34801561090257600080fd5b50610410600160a060020a03600435166133bd565b34801561092357600080fd5b5061041061358b565b34801561093857600080fd5b506106b8613649565b34801561094d57600080fd5b50610410600160a060020a0360043516602435613650565b34801561097157600080fd5b506106b86137e8565b34801561098657600080fd5b50610410600160a060020a03600435166137ef565b3480156109a757600080fd5b5061047561388e565b3480156109bc57600080fd5b50610410600160a060020a0360043516613893565b3480156109dd57600080fd5b506106b8613a49565b3480156109f257600080fd5b506106df613a4f565b348015610a0757600080fd5b50610410600435613a5e565b348015610a1f57600080fd5b50610410600160a060020a0360043516602435613bb1565b348015610a4357600080fd5b50610410600160a060020a0360043581169060243581169060443516613da6565b348015610a7057600080fd5b50610410614265565b348015610a8557600080fd5b506106df6145b5565b348015610a9a57600080fd5b50610aa36145c4565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610adf578181015183820152602001610ac7565b505050509050019250505060405180910390f35b348015610aff57600080fd5b5061047561461e565b348015610b1457600080fd5b50610410600435614623565b348015610b2c57600080fd5b506104106147a6565b348015610b4157600080fd5b506106b861461e565b600080808084600160a060020a0381161515610b9e576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600160a060020a038616600081815260086020526040902054600980549194509084908110610bc957fe5b600091825260209091200154600160a060020a031614610c21576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009805483908110610c2f57fe5b600091825260209091200154600a8054600160a060020a039092169184908110610c5557fe5b600091825260209091200154600b8054600160a060020a039092169185908110610c7b57fe5b6000918252602090912001549196509450600160a060020a0316925050509193909250565b60055460065460098054604080516020808402820181019092528281526060958695869560009586959194600a94600b94600160a060020a03918216949116928791830182828015610d1b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610cfd575b5050505050945083805480602002602001604051908101604052809291908181526020018280548015610d7757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610d59575b5050505050935082805480602002602001604051908101604052809291908181526020018280548015610dd357602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610db5575b50505050509250945094509450945094509091929394565b606060008060015481805480602002602001604051908101604052809291908181526020018280548015610e4857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610e2a575b50505050509150915091509091565b600082600160a060020a0381161515610ea8576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515610eff576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610f3e57600080fd5b505af1158015610f52573d6000803e3d6000fd5b505050506040513d6020811015610f6857600080fd5b505114610fbf576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b610fd66006600160a060020a038716866000614af1565b9250610fef6006600160a060020a038716866000614bc4565b610ff8836151f5565b80156110235750600160008481526003602052604090206006015460ff16600481111561102157fe5b145b156110315761103183615210565b5050505050565b600080333014611080576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff1615156110e2576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6000546110f690600163ffffffff61591416565b6001546032821115801561110a5750818111155b801561111557508015155b801561112057508115155b1515611164576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b60008054600160a060020a0388168252600260205260408220805460ff19169055955093505b6001850384101561123d5785600160a060020a03166000858154811015156111ae57fe5b600091825260209091200154600160a060020a03161415611232576000805460001987019081106111db57fe5b60009182526020822001548154600160a060020a039091169190869081106111ff57fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a0316021790555061123d565b60019093019261118a565b60008054600019870190811061124f57fe5b600091825260208220018054600160a060020a03191690555461127990600163ffffffff61591416565b611284600082615a01565b5061128d61216f565b604051600160a060020a038716907f1af6bd3d85a56e7c4a0700756fd2ca3b3b65c266e56c77652c5a346bc256522090600090a2505050505050565b6000333014611310576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600954600010156113ae57600160a060020a03841660008181526008602052604090205460098054909190811061134357fe5b600091825260209091200154600160a060020a031614156113ae576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b83600160a060020a031683600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156113f657600080fd5b505af115801561140a573d6000803e3d6000fd5b505050506040513d602081101561142057600080fd5b5051600160a060020a03161461146e576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b81600160a060020a031683600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156114b657600080fd5b505af11580156114ca573d6000803e3d6000fd5b505050506040513d60208110156114e057600080fd5b5051600160a060020a031614611540576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b82600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561157e57600080fd5b505af1158015611592573d6000803e3d6000fd5b505050506040513d60208110156115a857600080fd5b50511515600114611603576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b5060098054600160a060020a03808616600081815260086020908152604080832086905560018087019097557f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af86018054600160a060020a03199081168617909155600a8054808a019091557fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80180548b88169083168117909155600b8054998a0181559094527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99097018054958916959097168517909655855192835282015280840191909152915190917fe01726557c1ea9f7286dca4bba890e96fea9041689db298806306cafa74c9e91919081900360600190a150505050565b600281565b3360008181526002602052604081205490919060ff16151561177f576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b611796600a600160a060020a038516600080614af1565b91506117af600a600160a060020a038516600080614bc4565b6117b8826151f5565b80156117e35750600160008381526003602052604090206006015460ff1660048111156117e157fe5b145b156117f1576117f182615210565b505050565b33600081815260026020526040812054909182918291829160ff161515611855576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b61186189898989614af1565b60008181526003602052604090206005015490955015156118cc576040805160e560020a62461bcd02815260206004820152601060248201527f496e76616c696420726571756573742e00000000000000000000000000000000604482015290519081900360640190fd5b600160008681526003602052604090206006015460ff1660048111156118ee57fe5b14611943576040805160e560020a62461bcd02815260206004820152601f60248201527f4d757374206265206174206e6f742d636f6e6669726d65642073746174652e00604482015290519081900360640190fd5b60008581526003602052604081206004015490945092508391505b82821015611e2657600085815260036020526040902060040180548390811061198357fe5b600091825260209091200154600160a060020a0316331415611e1b57600085815260036020526040902060050154600194504262093a809091011015611b2f5760008581526003602052604090206005015442621275009091011015611a3e576119ec8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611a266004830182615a25565b5060006005820155600601805460ff19169055611a5b565b6000858152600360205260409020600601805460ff191660041790555b600085815260036020526040908190209051339187917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918d918d918d918d91600401908086600a811115611aac57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611b1857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611afa575b5050965050505050505060405180910390a3611e16565b60001983018214611bc057600085815260036020526040902060040180546000198501908110611b5b57fe5b6000918252602080832090910154878352600390915260409091206004018054600160a060020a039092169184908110611b9157fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055505b600085815260036020526040902060040180546000198501908110611be157fe5b600091825260208083209091018054600160a060020a0319169055868252600390526040902060040154611c1c90600163ffffffff61591416565b6000868152600360205260409020611c379060040182615a01565b5060008581526003602081905260409182902080546001820154600283015493830154945133958b957f9c174b2536ba49e3478ca649dac74d9e9f71f70adf70f193e780eabbfcdc367c9560ff9095169490926004909101908086600a811115611c9d57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611d0957602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611ceb575b5050965050505050505060405180910390a36000858152600360205260409020600401541515611e1657611d3c8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611d766004830182615a25565b506000600582018190556006909101805460ff19169055858152600360208190526040918290208054600182015460028301549290930154935133948a947fbfda049a0206fd9c90ed4a3170f5bfaad83c323a16835dd68fea92faa247c2cd9460ff9094169390929091908085600a811115611dee57fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a35b611e26565b60019091019061195e565b831515611e7d576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220686173206e6f74207265717565737465642e000000604482015290519081900360640190fd5b505050505050505050565b600060606000806000611e9d89898989614af1565b600081815260036020908152604091829020600581015460068201546004909201805485518186028101860190965280865295965086959094919360ff90931692859190830182828015611f1a57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611efc575b50505050509250945094509450945050945094509450949050565b600481565b600c5460ff1681565b60408051808201909152600b81527f41646472657373426f6f6b000000000000000000000000000000000000000000602082015281565b600080333014611fc2576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561200157600080fd5b505af1158015612015573d6000803e3d6000fd5b505050506040513d602081101561202b57600080fd5b505114612082576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060068054600160a060020a03848116600160a060020a031983161790925516600081156121185781600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156120e957600080fd5b505af11580156120fd573d6000803e3d6000fd5b505050506040513d602081101561211357600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517ffdccdf242038c2d09605009fbb95e03f75cdbd106d0a9e52a1670be9553c88489181900360800190a150505050565b6000803330146121b7576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b505060045460005b8181101561223b57600360006004838154811015156121da57fe5b600091825260208083209091015483528201929092526040018120805460ff19168155600181018290556002810182905560038101829055906122206004830182615a25565b5060006005820155600601805460ff191690556001016121bf565b61224760046000615a25565b6040517f907527d30089abd16e30f06ddbbbc18480505176262f19bc16c1fbf9262f9c6b90600090a15050565b600c54610100900460ff1681565b60003330146122c9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b50600160a060020a03811660008181526008602052604090205460098054919291839081106122f457fe5b600091825260209091200154600160a060020a03161461234c576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009546001106123a6576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b6009546000190181101561251e576009805460001981019081106123c657fe5b60009182526020909120015460098054600160a060020a0390921691839081106123ec57fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600a805490916000190190811061242b57fe5b600091825260209091200154600a8054600160a060020a03909216918390811061245157fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600b805490916000190190811061249057fe5b600091825260209091200154600b8054600160a060020a0390921691839081106124b657fe5b600091825260208220018054600160a060020a031916600160a060020a039390931692909217909155600980548392600892909160001981019081106124f857fe5b6000918252602080832090910154600160a060020a031683528201929092526040019020555b600160a060020a03821660009081526008602052604081205560098054600019810190811061254957fe5b60009182526020909120018054600160a060020a031916905560095461257690600163ffffffff61591416565b612581600982615a01565b50600a8054600019810190811061259457fe5b60009182526020909120018054600160a060020a0319169055600a546125c190600163ffffffff61591416565b6125cc600a82615a01565b50600b805460001981019081106125df57fe5b60009182526020909120018054600160a060020a0319169055600b5461260c90600163ffffffff61591416565b612617600b82615a01565b5060408051600160a060020a038416815290517fa30079721e55931e89e7cdb421712ad0fcc817e7cac8fe954aa7ed0d46b9c42d9181900360200190a15050565b603281565b600754600160a060020a031681565b3330146126b1576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038116600090815260026020526040902054819060ff1615612724576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b60005461273890600163ffffffff6159e816565b6001546032821115801561274c5750818111155b801561275757508015155b801561276257508115155b15156127a6576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600160a060020a0384166000818152600260205260408120805460ff19166001908117909155815490810182559080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563018054600160a060020a031916909117905561281161216f565b604051600160a060020a038516907fad6de4452a631e641cb59902236607946ce9272b9b981f2f80e8d129cb9084ba90600090a250505050565b600c54606090819081908190600090819060ff161515612884576040805160008082526020820190815281830190925294509250612b2f565b6009805490506003026002016040519080825280602002602001820160405280156128b9578160200160208202803883390190505b5093506009805490506003026002016040519080825280602002602001820160405280156128f1578160200160208202803883390190505b506009549093509150600090505b81811015612a71576000848260030281518110151561291a57fe5b60ff909216602092830290910190910152600980548290811061293957fe5b6000918252602090912001548351600160a060020a039091169084906003840290811061296257fe5b600160a060020a039092166020928302909101909101528351600190859060038402830190811061298f57fe5b60ff909216602092830290910190910152600a8054829081106129ae57fe5b6000918252602090912001548351600160a060020a03909116908490600384026001019081106129da57fe5b600160a060020a0390921660209283029091019091015283516002908590600384028301908110612a0757fe5b60ff909216602092830290910190910152600b805482908110612a2657fe5b6000918252602090912001548351600160a060020a0390911690849060026003850201908110612a5257fe5b600160a060020a039092166020928302909101909101526001016128ff565b60038483600302815181101515612a8457fe5b60ff9092166020928302909101909101526005548351600160a060020a0390911690849060038502908110612ab557fe5b600160a060020a039092166020928302909101909101528351600490859060016003860201908110612ae357fe5b60ff9092166020928302909101909101526006548351600160a060020a0390911690849060038502600101908110612b1757fe5b600160a060020a039092166020928302909101909101525b509194909350915050565b600081565b606481565b600080838360328211801590612b5a5750818111155b8015612b6557508015155b8015612b7057508115155b1515612bb4576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b7388bb3838aa0a140acb73eeb3d4b25a8d3afd58d43314612c1f576040805160e560020a62461bcd02815260206004820152600f60248201527f496e76616c69642073656e6465722e0000000000000000000000000000000000604482015290519081900360640190fd5b600c54610100900460ff1615612c7f576040805160e560020a62461bcd02815260206004820152601460248201527f416c726561647920636f6e73747275637465642e000000000000000000000000604482015290519081900360640190fd5b600c805460ff19169055859350600092505b83831015612da65760026000888886818110612ca957fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff16158015612d015750868684818110612ce357fe5b90506020020135600160a060020a0316600160a060020a0316600014155b1515612d57576040805160e560020a62461bcd02815260206004820152601e60248201527f41646472657373206973206e756c6c206f72206e6f7420756e697175652e0000604482015290519081900360640190fd5b600160026000898987818110612d6957fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff191691151591909117905560019290920191612c91565b612db260008888615a46565b506001859055600c805461ff001916610100179055604080518082018252600b81527f41646472657373426f6f6b00000000000000000000000000000000000000000060208083019182528351938401899052606080855283519085015282517fc5caa942b8f8ea45a2e094d941dbba0ef9c0307f34c81ce78e71bfb128d6b25a946000938b939192839283019160808401918083838a5b83811015612e62578181015183820152602001612e4a565b50505050905090810190601f168015612e8f5780820380516001836020036101000a031916815260200191505b508381038252858181548152602001915080548015612ed757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311612eb9575b50509550505050505060405180910390a150505050505050565b600160a060020a038116600090815260026020526040812054829060ff161515612f53576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515612faa576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b83600160a060020a0381161515612ff9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b60005461300d90600163ffffffff61591416565b600154603282111580156130215750818111155b801561302c57508015155b801561303757508115155b151561307b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6130926002600160a060020a038916600080614af1565b95506130ab6002600160a060020a038916600080614bc4565b6130b4866151f5565b80156130df5750600160008781526003602052604090206006015460ff1660048111156130dd57fe5b145b156130ed576130ed86615210565b50505050505050565b60008181526003602081815260408084208054600182015460028301549583015460058401546006850154600490950180548751818a0281018a019098528088528a998a998a996060998b998a9960ff91821699909895979096909594919092169291859183018282801561319457602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311613176575b505050505092509650965096509650965096509650919395979092949650565b60008080808085600160a060020a0381161515613209576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600a546000965086955093508492505b8383101561326457600a80543391908590811061323257fe5b600091825260209091200154600160a060020a031614156132595760019550829450613264565b600190920191613219565b8515156132bb576040805160e560020a62461bcd02815260206004820152601e60248201527f4d73672e73656e646572206973206e6f7420434e20636f6e74726163742e0000604482015290519081900360640190fd5b600b8054869081106132c957fe5b600091825260209091200154600b8054600160a060020a0390921693508891879081106132f257fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055507faa5c92ffd739bc0b8b117b671e7d713917ddb1440b436263a3ea106d70c6f05f60098681548110151561335057fe5b600091825260209091200154600b8054600160a060020a03909216918591908990811061337957fe5b6000918252602091829020015460408051600160a060020a03958616815293851692840192909252929092168183015290519081900360600190a150505050505050565b3360008181526002602052604081205490919060ff161515613417576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff161561348a576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b83600160a060020a03811615156134d9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b6000546134ed90600163ffffffff6159e816565b600154603282111580156135015750818111155b801561350c57508015155b801561351757508115155b151561355b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6135726001600160a060020a038916600080614af1565b95506130ab6001600160a060020a038916600080614bc4565b3360008181526002602052604081205490919060ff1615156135e5576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6135f3600460008080614af1565b9150613603600460008080614bc4565b61360c826151f5565b80156136375750600160008381526003602052604090206006015460ff16600481111561363557fe5b145b156136455761364582615210565b5050565b62093a8081565b600082600160a060020a03811615156136a1576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff1615156136f8576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561373757600080fd5b505af115801561374b573d6000803e3d6000fd5b505050506040513d602081101561376157600080fd5b5051146137b8576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b6137cf6007600160a060020a038716866000614af1565b9250610fef6007600160a060020a038716866000614bc4565b6212750081565b333014613834576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b60078054600160a060020a031916600160a060020a03838116919091179182905560408051929091168252517f508aacd44cfe23a34a8c2643ab08c3410cf5505632cfce58dcfa0efa2fd2ff37916020908290030190a150565b600381565b60008082600160a060020a03811615156138e5576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff16151561393c576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a03851660008181526008602052604090205460098054919650908690811061396757fe5b600091825260209091200154600160a060020a0316146139bf576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b600954600110613a19576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b613a306009600160a060020a038716600080614af1565b9250610fef6009600160a060020a038716600080614bc4565b60015481565b600654600160a060020a031681565b333014613aa3576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b6000548160328211801590613ab85750818111155b8015613ac357508015155b8015613ace57508115155b1515613b12576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154831415613b6c576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b6001839055613b7961216f565b6040805184815290517f8951393946c27b45080aad111464c16c70f3d5e7d24b114a627334441961bf5f9181900360200190a1505050565b600080333014613bf9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505050506040513d6020811015613c6257600080fd5b505114613cb9576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060058054600160a060020a03848116600160a060020a03198316179092551660008115613d4f5781600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613d2057600080fd5b505af1158015613d34573d6000803e3d6000fd5b505050506040513d6020811015613d4a57600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517fd531725ac89042f190fd73adfdeff435e07500f1a92b4b87743f1bcf91acb3a79181900360800190a150505050565b600083600160a060020a0381161515613df7576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e46576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e95576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515613eec576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60095460001015613f8a57600160a060020a038816600081815260086020526040902054600980549091908110613f1f57fe5b600091825260209091200154600160a060020a03161415613f8a576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b87600160a060020a031687600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613fd257600080fd5b505af1158015613fe6573d6000803e3d6000fd5b505050506040513d6020811015613ffc57600080fd5b5051600160a060020a03161461404a576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b85600160a060020a031687600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561409257600080fd5b505af11580156140a6573d6000803e3d6000fd5b505050506040513d60208110156140bc57600080fd5b5051600160a060020a03161461411c576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b86600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561415a57600080fd5b505af115801561416e573d6000803e3d6000fd5b505050506040513d602081101561418457600080fd5b505115156001146141df576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b6141fb6008600160a060020a03808b16908a8116908a16614af1565b94506142196008600160a060020a03808b16908a8116908a16614bc4565b614222856151f5565b801561424d5750600160008681526003602052604090206006015460ff16600481111561424b57fe5b145b1561425b5761425b85615210565b5050505050505050565b3330146142aa576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600c5460ff1615614305576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b600054151561435e576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156143c0576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614422576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600954151561447b576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a54600954146144fc576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a541461457d576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b600c805460ff191660011790556040517f29d89931226d613bf878a0be8c635eaf2049121c8c68d5ad80a78f0ac9005d4b90600090a1565b600554600160a060020a031681565b6060600480548060200260200160405190810160405280929190818152602001828054801561461357602002820191906000526020600020905b815481526001909101906020018083116145fe575b505050505090505b90565b600181565b3360008181526002602052604081205490919060ff16151561467d576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60005483603282118015906146925750818111155b801561469d57508015155b80156146a857508115155b15156146ec576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154851415614746576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b614754600386600080614af1565b9350614764600386600080614bc4565b61476d846151f5565b80156147985750600160008581526003602052604090206006015460ff16600481111561479657fe5b145b156110315761103184615210565b3360008181526002602052604081205490919060ff161515614800576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600c5460ff161561485b576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b60005415156148b4576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a03161515614916576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614978576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b60095415156149d1576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a5460095414614a52576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a5414614ad3576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b614ae1600560008080614af1565b9150613603600560008080614bc4565b6000848484846040516020018085600a811115614b0a57fe5b60ff167f010000000000000000000000000000000000000000000000000000000000000002815260018101949094525060218301919091526041808301919091526040805180840390920182526061909201918290528051909250819060208401908083835b60208310614b8f5780518252601f199092019160209182019101614b70565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912098975050505050505050565b6000806000614bd587878787614af1565b60008181526003602052604090206005015490935015614f5e5760008381526003602052604090206005015442621275009091011015614c6a57614c188361592b565b60008381526003602081905260408220805460ff19168155600181018390556002810183905590810182905590614c526004830182615a25565b5060006005820155600601805460ff19169055614f5e565b6000838152600360205260409020600501544262093a809091011015614da257600460008481526003602052604090206006015460ff166004811115614cac57fe5b14614cce576000838152600360205260409020600601805460ff191660041790555b600083815260036020526040908190209051339185917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918b918b918b918b91600401908086600a811115614d1f57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614d8b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614d6d575b5050965050505050505060405180910390a3614f5e565b6000838152600360205260409020600501544210614f5e575050600081815260036020526040812060040154905b81811015614e69576000838152600360205260409020600401805482908110614df557fe5b600091825260209091200154600160a060020a0316331415614e61576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220616c7265616479207265717565737465642e000000604482015290519081900360640190fd5b600101614dd0565b600083815260036020908152604080832060040180546001810182558185529284209092018054600160a060020a03191633908117909155928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a811115614edf57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614f4b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614f2d575b5050965050505050505060405180910390a35b60008381526003602052604090206005015415156130ed57600454606411614fe357600487600a811115614f8e57fe5b14614fe3576040805160e560020a62461bcd02815260206004820152601560248201527f52657175657374206c6973742069732066756c6c2e0000000000000000000000604482015290519081900360640190fd5b60e06040519081016040528088600a811115614ffb57fe5b81526020808201899052604080830189905260608301889052805160008082529281019091526080909201919050815242602082015260400160019052600084815260036020526040902081518154829060ff1916600183600a81111561505e57fe5b021790555060208281015160018301556040830151600283015560608301516003830155608083015180516150999260048501920190615aa9565b5060a0820151600582015560c082015160068201805460ff191660018360048111156150c157fe5b0217905550505060008381526003602090815260408083206004908101805460018181018355828752948620018054600160a060020a0319163390811790915582549485019092557f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909301879055928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a81111561516e57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c00190849080156151da57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116151bc575b5050965050505050505060405180910390a350505050505050565b60015460009182526003602052604090912060040154101590565b600061521a615afe565b600083815260036020526040808220815160e0810190925280549294509091829060ff16600a81111561524957fe5b600a81111561525457fe5b8152600182015460208083019190915260028301546040808401919091526003840154606084015260048401805482518185028101850190935280835260809094019391929091908301828280156152d557602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116152b7575b505050918352505060058201546020820152600682015460409091019060ff16600481111561530057fe5b600481111561530b57fe5b905250905060018151600a81111561531f57fe5b1415615389576020810151604080517f70480275000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916370480275916024808301926000929190829003018183875af192505050915061579a565b60028151600a81111561539857fe5b1415615402576020810151604080517f27e1f7df000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916327e1f7df916024808301926000929190829003018183875af192505050915061579a565b60038151600a81111561541157fe5b1415615473576020810151604080517fc47afb3a000000000000000000000000000000000000000000000000000000008152600481019290925251309163c47afb3a916024808301926000929190829003018183875af192505050915061579a565b60048151600a81111561548257fe5b14156154c55730600160a060020a0316634f97638f6040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60058151600a8111156154d457fe5b14156155175730600160a060020a031663cec924666040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60068151600a81111561552657fe5b141561559957602081015160408083015181517fc7e9de75000000000000000000000000000000000000000000000000000000008152600160a060020a039093166004840152602483015251309163c7e9de75916044808301926000929190829003018183875af192505050915061579a565b60078151600a8111156155a857fe5b141561561b57602081015160408083015181517f4c5d435c000000000000000000000000000000000000000000000000000000008152600160a060020a0390931660048401526024830152513091634c5d435c916044808301926000929190829003018183875af192505050915061579a565b60088151600a81111561562a57fe5b14156156ad576020810151604080830151606084015182517f298b3c61000000000000000000000000000000000000000000000000000000008152600160a060020a0394851660048201529184166024830152909216604483015251309163298b3c61916064808301926000929190829003018183875af192505050915061579a565b60098151600a8111156156bc57fe5b1415615726576020810151604080517f579740db000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163579740db916024808301926000929190829003018183875af192505050915061579a565b600a8151600a81111561573557fe5b141561579a576020810151604080517fafaaf330000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163afaaf330916024808301926000929190829003018183875af194505050505b6157a38361592b565b811561585d57600083815260036020526040902060050154156157dd576000838152600360205260409020600601805460ff191660021790555b33600160a060020a031683600019167fc55c9229184beabeee72b6970a96691b4200919e47579cc4b9be50a1bec7ef1183600001518460200151856040015186606001516040518085600a81111561583157fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a36117f1565b60008381526003602052604090206005015415615894576000838152600360208190526040909120600601805460ff191690911790555b33600160a060020a031683600019167ff151a3ee41626c2511372320f09f7957af81c8c1cde8cdf3f05a5979626eaaf383600001518460200151856040015186606001516040518085600a8111156158e857fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a3505050565b6000808383111561592457600080fd5b5050900390565b60045460005b818110156117f157600480548290811061594757fe5b6000918252602090912001548314156159e0576000198201811461599e5760048054600019840190811061597757fe5b906000526020600020015460048281548110151561599157fe5b6000918252602090912001555b6004805460001984019081106159b057fe5b60009182526020822001556004546159cf90600163ffffffff61591416565b6159da600482615a01565b506117f1565b600101615931565b6000828201838110156159fa57600080fd5b9392505050565b8154818355818111156117f1576000838152602090206117f1918101908301615b3b565b5080546000825590600052602060002090810190615a439190615b3b565b50565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a99578154600160a060020a031916600160a060020a03843516178255602090920191600190910190615a66565b50615aa5929150615b55565b5090565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a995782518254600160a060020a031916600160a060020a03909116178255602090920191600190910190615ac9565b6040805160e081019091528060008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b61461b91905b80821115615aa55760008155600101615b41565b61461b91905b80821115615aa5578054600160a060020a0319168155600101615b5b560041646472657373206973206e6f742061646d696e2e0000000000000000000000496e76616c696420434e206e6f64652049442e0000000000000000000000000041646472657373206973206e756c6c2e000000000000000000000000000000004e6f742061206d756c74697369672d7472616e73616374696f6e2e0000000000496e76616c696420726571756972656d656e742e000000000000000000000000a165627a7a7230582007320485103bb418db53fd7eb24f69d0f455d28d1b5b146eb3e7fa6237cb69f10029","balance":"0x0"},"73718c4980728857f3aa5148e9d1b471efa3a7dd":{"balance":"0x446c3b15f9926687d2c40534fdb564000000000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}' > /klaytn/genesis.json echo '["kni://b299bf5bb0b5e91ddf35e055bef9b69c38112bae582b589d4ab4955f6b0f8e790a182530bd57cff00cad1eb49c39d6ab66eaaa979c5d644681ee124649c5f00f@172.16.239.10:32323?discport=0\u0026ntype=cn"]' > /klaytn/static-nodes.json kcn --datadir "/klaytn" init "/klaytn/genesis.json" kcn \ diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Block.java b/core/src/main/java/com/klaytn/caver/methods/response/Block.java index e975bcff6..919f08e02 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Block.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Block.java @@ -133,10 +133,15 @@ public static class BlockData { */ private String voteData; + /** + * Base fee per gas + */ + private String baseFeePerGas; + public BlockData() { } - public BlockData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String totalBlockScore, String extraData, String size, String gasUsed, String timestamp, String timestampFoS, List transactions, String governanceData, String voteData) { + public BlockData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String totalBlockScore, String extraData, String size, String gasUsed, String timestamp, String timestampFoS, List transactions, String governanceData, String voteData, String baseFeePerGas) { this.number = number; this.hash = hash; this.parentHash = parentHash; @@ -146,14 +151,16 @@ public BlockData(String number, String hash, String parentHash, String logsBloom this.receiptsRoot = receiptsRoot; this.reward = reward; this.blockScore = blockScore; - this.totalBlockScore = totalBlockScore; this.extraData = extraData; - this.size = size; this.gasUsed = gasUsed; this.timestamp = timestamp; this.timestampFoS = timestampFoS; - this.transactions = transactions; this.governanceData = governanceData; + this.baseFeePerGas = baseFeePerGas; + // Below is non-existed in header + this.totalBlockScore = totalBlockScore; + this.size = size; + this.transactions = transactions; this.voteData = voteData; } @@ -302,6 +309,10 @@ public String getTotalBlockScore() { public void setTotalBlockScore(String totalBlockScore) { this.totalBlockScore = totalBlockScore; } + + public String getBaseFeePerGas() { return baseFeePerGas; } + + public void setBaseFeePerGas(String baseFeePerGas) { this.baseFeePerGas = baseFeePerGas; } } public static class ResponseDeserializer extends JsonDeserializer { diff --git a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java new file mode 100644 index 000000000..89f7f6380 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java @@ -0,0 +1,265 @@ +/* + * Copyright 2020 The caver-java Authors + * + * 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 com.klaytn.caver.methods.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.web3j.protocol.ObjectMapperFactory; +import org.web3j.protocol.core.Response; + +import java.io.IOException; + +public class BlockHeader extends Response { + + @Override + @JsonDeserialize(using = BlockHeader.ResponseDeserializer.class) + public void setResult(BlockHeaderData result) { super.setResult(result); } + + public static class BlockHeaderData { + /** + * The block number. null when its pending block + */ + private String number; // exist + + /** + * Hash of the block. null when its pending block + */ + private String hash; // exist + + /** + * Hash of the parent block + */ + private String parentHash; // exist + + /** + * The bloom filter for the logs of the block. null when its pending block + */ + private String logsBloom; // exist + + /** + * The root of the transaction trie of the block + */ + private String transactionsRoot; // exist + + /** + * The root of the final state trie of the block + */ + private String stateRoot; // exist + + /** + * The root of the receipts trie of the block + */ + private String receiptsRoot; // exist + + /** + * The address of the beneficiary to whom the block rewards were given. + */ + private String reward; // exist + + /** + * Former difficulty. Always 1 in the BFT consensus engine + */ + private String blockScore; // exist + + /** + * The "extra data" field of this block + */ + private String extraData; + + /** + * The total used gas by all transactions in this block + */ + private String gasUsed; + + /** + * The Unix timestamp for when the block was collated + */ + private String timestamp; + + /** + * The fraction of a second of the timestamp for when the block was collated + */ + private String timestampFoS; + + /** + * RLP encoded governance configuration + */ + private String governanceData; + + /** + * Base fee per gas + */ + private String baseFeePerGas; + + public BlockHeaderData() { + } + + public BlockHeaderData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String extraData, String gasUsed, String timestamp, String timestampFoS, String governanceData, String baseFeePerGas) { + this.number = number; + this.hash = hash; + this.parentHash = parentHash; + this.logsBloom = logsBloom; + this.transactionsRoot = transactionsRoot; + this.stateRoot = stateRoot; + this.receiptsRoot = receiptsRoot; + this.reward = reward; + this.blockScore = blockScore; + this.extraData = extraData; + this.gasUsed = gasUsed; + this.timestamp = timestamp; + this.timestampFoS = timestampFoS; + this.governanceData = governanceData; + this.baseFeePerGas = baseFeePerGas; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getParentHash() { + return parentHash; + } + + public void setParentHash(String parentHash) { + this.parentHash = parentHash; + } + + public String getLogsBloom() { + return logsBloom; + } + + public void setLogsBloom(String logsBloom) { + this.logsBloom = logsBloom; + } + + public String getTransactionsRoot() { + return transactionsRoot; + } + + public void setTransactionsRoot(String transactionsRoot) { + this.transactionsRoot = transactionsRoot; + } + + public String getStateRoot() { + return stateRoot; + } + + public void setStateRoot(String stateRoot) { + this.stateRoot = stateRoot; + } + + public String getReceiptsRoot() { + return receiptsRoot; + } + + public void setReceiptsRoot(String receiptsRoot) { + this.receiptsRoot = receiptsRoot; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + public String getGasUsed() { + return gasUsed; + } + + public void setGasUsed(String gasUsed) { + this.gasUsed = gasUsed; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getTimestampFoS() { + return timestampFoS; + } + + public void setTimestampFoS(String timestampFoS) { + this.timestampFoS = timestampFoS; + } + + public String getGovernanceData() { + return governanceData; + } + + public void setGovernanceData(String governanceData) { + this.governanceData = governanceData; + } + + public String getReward() { + return reward; + } + + public void setReward(String reward) { + this.reward = reward; + } + + public String getBlockScore() { + return blockScore; + } + + public void setBlockScore(String blockScore) { + this.blockScore = blockScore; + } + + public String getBaseFeePerGas() { return baseFeePerGas; } + + public void setBaseFeePerGas(String baseFeePerGas) { this.baseFeePerGas = baseFeePerGas; } + } + + public static class ResponseDeserializer extends JsonDeserializer { + + private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + + @Override + public BlockHeader.BlockHeaderData deserialize( + JsonParser jsonParser, + DeserializationContext deserializationContext) throws IOException { + if (jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) { + return objectReader.readValue(jsonParser, BlockHeader.BlockHeaderData.class); + } else { + return null; // null is wrapped by Optional in above getter + } + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index a72777414..2a5f50955 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -406,6 +406,72 @@ public Request getBlockNumber() { Quantity.class); } + /** + * Returns a block header by block hash. + * @param blockHash The hash of block. + * @return BlockHeader + */ + public Request getHeaderByHash(String blockHash) { + return new Request<>( + "klay_getHeaderByHash", + Arrays.asList(blockHash), + web3jService, + BlockHeader.class); + } + + /** + * Returns a block header by block number. + * @param blockNumber The block number. + * @return BlockHeader + */ + public Request getHeaderByNumber(long blockNumber) { + return new Request<>( + "klay_getHeaderByNumber", + Arrays.asList(blockNumber), + web3jService, + BlockHeader.class); + } + + /** + * Returns a block header by block number. + * @param defaultBlockParameter The string "latest", "earliest", or "pending". + * @return BlockHeader + */ + public Request getHeaderByNumber(DefaultBlockParameter defaultBlockParameter) { + return new Request<>( + "klay_getHeaderByNumber", + Arrays.asList(defaultBlockParameter), + web3jService, + BlockHeader.class); + } + + /** + * Returns a block header by block hash. + * @param blockHash The hash of block. + * @return BlockHeader + */ + public Request getHeader(String blockHash) { + return getHeaderByHash(blockHash); + } + + /** + * Returns a block header by block number. + * @param blockNumber The block number. + * @return BlockHeader + */ + public Request getHeader(long blockNumber) { + return getHeaderByNumber(blockNumber); + } + + /** + * Returns a block header by block number. + * @param blockTag The string "latest", "earliest", or "pending". + * @return BlockHeader + */ + public Request getHeader(DefaultBlockParameter blockTag) { + return getHeaderByNumber(blockTag); + } + /** * Returns information about a block by block number.

* It set "isFullTransaction" param to false. diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index c90a74df6..de1823b08 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -46,10 +46,7 @@ import org.junit.runner.RunWith; import org.web3j.protocol.ObjectMapperFactory; import org.web3j.protocol.Web3jService; -import org.web3j.protocol.core.DefaultBlockParameterName; -import org.web3j.protocol.core.DefaultBlockParameterNumber; -import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.Response; +import org.web3j.protocol.core.*; import org.web3j.protocol.exceptions.TransactionException; import org.web3j.utils.Numeric; @@ -592,6 +589,58 @@ public void getBlockNumberTest() { } } + @Test + public void getHeaderTest() { + try { + BlockHeader response = caver.rpc.klay.getHeader(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + String hash = blockHeader.getHash(); + assertNotNull(hash); + + response = caver.rpc.klay.getHeader(0).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + + response = caver.rpc.klay.getHeader(hash).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void getHeaderByNumberTest() { + try { + BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + + response = caver.rpc.klay.getHeaderByNumber(0).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void getHeaderByHashTest() { + try { + BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + + BlockHeader responseByHash = caver.rpc.klay.getHeaderByHash(blockHeader.getHash()).send(); + + assertEquals(blockHeader.getHash(), responseByHash.getResult().getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + @Test public void getBlockByNumberTest() { try { From a7472a59e3331952aa57747f07e60d96a3a4af89 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 25 Feb 2022 11:12:49 +0900 Subject: [PATCH 02/89] Revert Block class and Update BlockHeader BlockHeader does not need custom Deserializer. --- .../klaytn/caver/methods/response/Block.java | 17 ++++--- .../caver/methods/response/BlockHeader.java | 45 +++++++------------ 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Block.java b/core/src/main/java/com/klaytn/caver/methods/response/Block.java index 919f08e02..51dba92dd 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Block.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Block.java @@ -151,17 +151,16 @@ public BlockData(String number, String hash, String parentHash, String logsBloom this.receiptsRoot = receiptsRoot; this.reward = reward; this.blockScore = blockScore; + this.totalBlockScore = totalBlockScore; this.extraData = extraData; + this.size = size; this.gasUsed = gasUsed; this.timestamp = timestamp; this.timestampFoS = timestampFoS; - this.governanceData = governanceData; - this.baseFeePerGas = baseFeePerGas; - // Below is non-existed in header - this.totalBlockScore = totalBlockScore; - this.size = size; this.transactions = transactions; + this.governanceData = governanceData; this.voteData = voteData; + this.baseFeePerGas = baseFeePerGas; } public String getNumber() { @@ -310,9 +309,13 @@ public void setTotalBlockScore(String totalBlockScore) { this.totalBlockScore = totalBlockScore; } - public String getBaseFeePerGas() { return baseFeePerGas; } + public String getBaseFeePerGas() { + return baseFeePerGas; + } - public void setBaseFeePerGas(String baseFeePerGas) { this.baseFeePerGas = baseFeePerGas; } + public void setBaseFeePerGas(String baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } } public static class ResponseDeserializer extends JsonDeserializer { diff --git a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java index 89f7f6380..b7ddb21ce 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java @@ -30,54 +30,55 @@ public class BlockHeader extends Response { @Override - @JsonDeserialize(using = BlockHeader.ResponseDeserializer.class) - public void setResult(BlockHeaderData result) { super.setResult(result); } + public void setResult(BlockHeaderData result) { + super.setResult(result); + } public static class BlockHeaderData { /** * The block number. null when its pending block */ - private String number; // exist + private String number; /** * Hash of the block. null when its pending block */ - private String hash; // exist + private String hash; /** * Hash of the parent block */ - private String parentHash; // exist + private String parentHash; /** * The bloom filter for the logs of the block. null when its pending block */ - private String logsBloom; // exist + private String logsBloom; /** * The root of the transaction trie of the block */ - private String transactionsRoot; // exist + private String transactionsRoot; /** * The root of the final state trie of the block */ - private String stateRoot; // exist + private String stateRoot; /** * The root of the receipts trie of the block */ - private String receiptsRoot; // exist + private String receiptsRoot; /** * The address of the beneficiary to whom the block rewards were given. */ - private String reward; // exist + private String reward; /** * Former difficulty. Always 1 in the BFT consensus engine */ - private String blockScore; // exist + private String blockScore; /** * The "extra data" field of this block @@ -242,24 +243,12 @@ public void setBlockScore(String blockScore) { this.blockScore = blockScore; } - public String getBaseFeePerGas() { return baseFeePerGas; } - - public void setBaseFeePerGas(String baseFeePerGas) { this.baseFeePerGas = baseFeePerGas; } - } - - public static class ResponseDeserializer extends JsonDeserializer { - - private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + public String getBaseFeePerGas() { + return baseFeePerGas; + } - @Override - public BlockHeader.BlockHeaderData deserialize( - JsonParser jsonParser, - DeserializationContext deserializationContext) throws IOException { - if (jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) { - return objectReader.readValue(jsonParser, BlockHeader.BlockHeaderData.class); - } else { - return null; // null is wrapped by Optional in above getter - } + public void setBaseFeePerGas(String baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; } } } From ce48944bab6b6f3a04cdc5924a0b8b844c2b01f4 Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 26 Feb 2022 16:46:01 +0900 Subject: [PATCH 03/89] Implement FeeHistory --- .../caver/methods/response/BlockHeader.java | 2 +- .../caver/methods/response/FeeHistory.java | 84 +++++++++++++++++++ .../main/java/com/klaytn/caver/rpc/Klay.java | 47 +++++++++++ .../com/klaytn/caver/common/rpc/RpcTest.java | 75 +++++++++++++++++ 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java diff --git a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java index b7ddb21ce..92148398b 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The caver-java Authors + * Copyright 2022 The caver-java Authors * * Licensed under the Apache License, Version 2.0 (the “License”); * you may not use this file except in compliance with the License. diff --git a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java new file mode 100644 index 000000000..68dd544c4 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.methods.response; + +import org.web3j.protocol.core.Response; + +import java.util.List; + +public class FeeHistory extends Response { + + @Override + public void setResult(FeeHistory.FeeHistoryData result) { + super.setResult(result); + } + + public static class FeeHistoryData { + /** + * Lowest number block of returned range. + */ + private String oldestBlock; + + /** + * An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block.

+ * Zeroes are returned for pre-EIP-1559 blocks. + */ + private List baseFeePerGas; + + /** + * A two-dimensional array of effective priority fees per gas at the requested block percentiles. + */ + private List> reward; + + /** + * An array of gasUsed/gasLimit in the block. + */ + private List gasUsedRatio; + + public String getOldestBlock() { + return oldestBlock; + } + + public void setOldestBlock(String oldestBlock) { + this.oldestBlock = oldestBlock; + } + + public List getBaseFeePerGas() { + return baseFeePerGas; + } + + public void setBaseFeePerGas(List baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } + + public List> getReward() { + return reward; + } + + public void setReward(List> reward) { + this.reward = reward; + } + + public List getGasUsedRatio() { + return gasUsedRatio; + } + + public void setGasUsedRatio(List gasUsedRatio) { + this.gasUsedRatio = gasUsedRatio; + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index 2a5f50955..06570e365 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.List; public class Klay { @@ -1387,6 +1388,52 @@ public Request sha3(String data) { Bytes.class); } + /** + * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. + *

Example :
+     * {@code
+     *  long blockCount = 5;
+     *  // Use block number from Transaction Receipt data.
+     *  long lastBlock = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue();
+     *  List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     *  FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, lastBlock, rewardPercentiles).send();
+     * }
+     * 
+ * @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + * @param lastBlock Highest number block (or block tag string) of the requested range. + * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) + * @return + */ + public Request getFeeHistory(long blockCount, long lastBlock, List rewardPercentiles) { + return new Request<>( + "klay_feeHistory", + Arrays.asList(blockCount, lastBlock, rewardPercentiles), + web3jService, + FeeHistory.class); + } + + /** + * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. + *
Example :
+     * {@code
+     *  long blockCount = 5;
+     *  List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     *  FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send();
+     * }
+     * 
+ * @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + * @param lastBlock Highest number block (or block tag string) of the requested range. + * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) + * @return + */ + public Request getFeeHistory(long blockCount, DefaultBlockParameter lastBlock, List rewardPercentiles) { + return new Request<>( + "klay_feeHistory", + Arrays.asList(blockCount, lastBlock, rewardPercentiles), + web3jService, + FeeHistory.class); + } + /** * Creates a new subscription to specific events by using RPC Pub/Sub over Websockets.

* diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index de1823b08..072633bfe 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -29,6 +29,7 @@ import com.klaytn.caver.methods.response.Account; import com.klaytn.caver.methods.response.Boolean; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.response.PollingTransactionReceiptProcessor; import com.klaytn.caver.transaction.response.TransactionReceiptProcessor; import com.klaytn.caver.transaction.type.FeeDelegatedValueTransfer; @@ -53,9 +54,11 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import static com.klaytn.caver.base.LocalValues.LOCAL_CHAIN_ID; import static com.klaytn.caver.base.LocalValues.LOCAL_NETWORK_ID; @@ -888,6 +891,78 @@ public void getGasPriceAtTest() throws IOException { assertEquals(new BigInteger("5d21dba00", 16), result); // 25,000,000,000 peb = 25 Gpeb } + // checkFeeHistoryResult checks response from getFeeHistory is ok or not. + private void checkFeeHistoryResult(FeeHistory feeHistory, long blockCount, List rewardPercentiles) { + FeeHistory.FeeHistoryData feeHistoryData = feeHistory.getResult(); + assertEquals(blockCount + 1, feeHistoryData.getBaseFeePerGas().size()); + + List> reward = feeHistoryData.getReward(); + if (reward != null) { + assertEquals(blockCount, reward.size()); + Consumer> consumer = rewardElement -> { + int expectedSize = 0 ; + if (rewardPercentiles != null) { + expectedSize = rewardPercentiles.size(); + } + assertEquals(expectedSize, rewardElement.size()); + }; + reward.forEach(consumer); + } + + assertEquals(blockCount, feeHistoryData.getGasUsedRatio().size()); + } + + @Test + public void getFeeHistoryTest() throws IOException, InterruptedException { + SingleKeyring sender = caver.wallet.keyring.createFromKlaytnWalletKey(LUMAN.getKlaytnWalletKey()); + caver.wallet.add(sender); + Quantity transactionCount = caver.rpc.klay.getTransactionCount(sender.getAddress()).send(); + BigInteger nonce = transactionCount.getValue(); + String gasPrice = klay.getGasPrice().send().getResult(); + + String chainId = klay.getChainID().send().getResult(); + final int txsCount = 30; + TransactionReceipt.TransactionReceiptData receiptData = null; + for (int i = 0; i < txsCount; i++) { + ValueTransfer tx = caver.transaction.valueTransfer.create( + TxPropertyBuilder.valueTransfer() + .setFrom(sender.getAddress()) + .setTo(caver.wallet.keyring.generate().getAddress()) + .setGas("0x99999") + .setGasPrice(gasPrice) + .setValue("0x1") + .setNonce(nonce) + .setChainId(chainId) + ); + caver.wallet.sign(sender.getAddress(), tx); + nonce = nonce.add(BigInteger.valueOf(1)); + + if (i != txsCount - 1) { + caver.rpc.klay.sendRawTransaction(tx).send(); + continue; + } + Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(tx).send(); + Thread.sleep(5000); + String txHash = sendResult.getResult(); + TransactionReceipt receipt = caver.rpc.klay.getTransactionReceipt(txHash).send(); + receiptData = receipt.getResult(); + } + if (receiptData == null) { + fail(); + } + + long blockCount = 5; + long blockNumber = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue(); + List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f)); + FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, blockNumber, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistory, blockCount, rewardPercentiles); + + blockCount = 5; + rewardPercentiles = null; + feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistory, blockCount, rewardPercentiles); + } + @Test public void isParallelDbWriteTest() throws Exception { Boolean response = caver.rpc.klay.isParallelDBWrite().send(); From 736299d9c0ba23122b2304c5d6eac5d626b7a046 Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 26 Feb 2022 21:34:46 +0900 Subject: [PATCH 04/89] Add javadoc examples to getHeader APIs --- .../main/java/com/klaytn/caver/rpc/Klay.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index 06570e365..0f58436ec 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -409,6 +409,13 @@ public Request getBlockNumber() { /** * Returns a block header by block hash. + *

Example :
+     * {@code
+     *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
+     *  BlockHeader response = caver.rpc.klay.getHeaderByHash(blockHash);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param blockHash The hash of block. * @return BlockHeader */ @@ -422,6 +429,12 @@ public Request getHeaderByHash(String blockHash) { /** * Returns a block header by block number. + *
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(5);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param blockNumber The block number. * @return BlockHeader */ @@ -435,6 +448,12 @@ public Request getHeaderByNumber(long blockNumber) { /** * Returns a block header by block number. + *
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param defaultBlockParameter The string "latest", "earliest", or "pending". * @return BlockHeader */ @@ -448,6 +467,13 @@ public Request getHeaderByNumber(DefaultBlockParameter defaultBl /** * Returns a block header by block hash. + *
Example :
+     * {@code
+     *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
+     *  BlockHeader response = caver.rpc.klay.getHeader(blockHash);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param blockHash The hash of block. * @return BlockHeader */ @@ -457,6 +483,12 @@ public Request getHeader(String blockHash) { /** * Returns a block header by block number. + *
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(5);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param blockNumber The block number. * @return BlockHeader */ @@ -466,6 +498,12 @@ public Request getHeader(long blockNumber) { /** * Returns a block header by block number. + *
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
* @param blockTag The string "latest", "earliest", or "pending". * @return BlockHeader */ From db3f068632e1d3147acf3bdf2fb4ef73df164343 Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 27 Feb 2022 14:33:25 +0900 Subject: [PATCH 05/89] Add getMaxPriorityFeePerGas --- core/src/main/java/com/klaytn/caver/rpc/Klay.java | 13 +++++++++++++ .../java/com/klaytn/caver/common/rpc/RpcTest.java | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index 0f58436ec..d7c3fc9f8 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -1242,6 +1242,19 @@ public Request getGasPriceAt() { ); } + /** + * Returns a suggestion for a gas tip cap for dynamic fee transactions in peb.

+ * Note: Since Klaytn has a fixed gas price, this `caver.rpc.klay.getMaxPriorityFeePerGas` returns the gas price set by Klaytn. + * @return Quantity + */ + public Request getMaxPriorityFeePerGas() { + return new Request<>( + "klay_maxPriorityFeePerGas", + Collections.emptyList(), + web3jService, + Quantity.class); + } + /** * Returns the unit price of the given block in peb.

* NOTE: This API has different behavior from Ethereum's and returns a gas price of Klaytn instead of suggesting a gas price as in Ethereum. diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index 072633bfe..68db9ec15 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -891,6 +891,13 @@ public void getGasPriceAtTest() throws IOException { assertEquals(new BigInteger("5d21dba00", 16), result); // 25,000,000,000 peb = 25 Gpeb } + @Test + public void getMaxPriorityFeePerGasTest() throws Exception { + Quantity response = caver.rpc.klay.getMaxPriorityFeePerGas().send(); + BigInteger result = response.getValue(); + assertEquals(new BigInteger("5d21dba00", 16), result); // 25,000,000,000 peb = 25 Gpeb + } + // checkFeeHistoryResult checks response from getFeeHistory is ok or not. private void checkFeeHistoryResult(FeeHistory feeHistory, long blockCount, List rewardPercentiles) { FeeHistory.FeeHistoryData feeHistoryData = feeHistory.getResult(); From 885f22668e51e6aa3e2e639564cadc34d2ee49ef Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 27 Feb 2022 23:10:38 +0900 Subject: [PATCH 06/89] Update legacy test cases These test cases must be updated because now we use docker images latest dev branch. --- core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java index 1dd8f753b..f441c1e5f 100644 --- a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java @@ -337,7 +337,7 @@ public void testEstimateGas() throws Exception { ); Quantity response = caver.klay().estimateGas(callObject).send(); String result = response.getResult(); - assertEquals("0x5318", result); + assertEquals("0x5398", result); } @Test From 1b9151b7b5e91e330f68326cef91539c6782468f Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 02:23:50 +0900 Subject: [PATCH 07/89] Add createAccessList and add javadoc for getMaxPriorityFeePerGas --- .../methods/response/AccessListResult.java | 101 +++++++++++++ .../main/java/com/klaytn/caver/rpc/Klay.java | 99 ++++++++++++- .../caver/transaction/type/AccessTuple.java | 138 ++++++++++++++++++ .../com/klaytn/caver/common/rpc/RpcTest.java | 31 ++++ 4 files changed, 361 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java create mode 100644 core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java new file mode 100644 index 000000000..5d9cf6ab2 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.methods.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.klaytn.caver.transaction.type.AccessTuple; +import org.web3j.protocol.core.Response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class AccessListResult extends Response { + + @Override + public void setResult(AccessListResultData result) { + super.setResult(result); + } + + @JsonDeserialize(using = AccessListResultData.AccessListResultDeserializer.class) + public static class AccessListResultData { + List accessList; + String error; + String gasUsed; + + public AccessListResultData(List accessList, String error, String gasUsed) { + this.accessList = accessList; + this.error = error; + this.gasUsed = gasUsed; + } + + public List getAccessList() { + return accessList; + } + + public void setAccessList(List accessList) { + this.accessList = accessList; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getGasUsed() { + return gasUsed; + } + + public void setGasUsed(String gasUsed) { + this.gasUsed = gasUsed; + } + + public static class AccessListResultDeserializer extends JsonDeserializer { + @Override + public AccessListResultData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode node = p.getCodec().readTree(p); + + List accessList = new ArrayList<>(); + JsonNode accessListNode = node.get("accessList"); + for (JsonNode innerNode : accessListNode) { + AccessTuple accessTuple = objectMapper.treeToValue(innerNode, AccessTuple.class); + accessList.add(accessTuple); + } + + AccessListResultData accessListResultData = new AccessListResultData(accessList, null, "0x0"); + JsonNode error = node.get("error"); + if (error != null) { + accessListResultData.setError(error.textValue()); + } + JsonNode gasUsed = node.get("gasUsed"); + if (gasUsed != null) { + accessListResultData.setGasUsed(gasUsed.textValue()); + } + return accessListResultData; + } + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index d7c3fc9f8..7491602fa 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -1245,6 +1245,14 @@ public Request getGasPriceAt() { /** * Returns a suggestion for a gas tip cap for dynamic fee transactions in peb.

* Note: Since Klaytn has a fixed gas price, this `caver.rpc.klay.getMaxPriorityFeePerGas` returns the gas price set by Klaytn. + *

+     * {@code
+     *
+     * Quantity response = caver.rpc.klay.getMaxPriorityFeePerGas().send();
+     * BigInteger result = response.getValue();
+     *
+     * }
+     * 
* @return Quantity */ public Request getMaxPriorityFeePerGas() { @@ -1439,15 +1447,88 @@ public Request sha3(String data) { Bytes.class); } + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
+     * {@code
+     *
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param defaultBlockParameter A block number, or the block tag string `latest` or `earliest`. If omitted, `latest` will be used. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, DefaultBlockParameter defaultBlockParameter) { + if (defaultBlockParameter == null) { + defaultBlockParameter = DefaultBlockParameterName.LATEST; + } + return new Request<>( + "klay_createAccessList", + Arrays.asList(callObject, defaultBlockParameter), + web3jService, + AccessListResult.class + ); + } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
+     * {@code
+     *
+     * String blockHash = "0x421440aef6024e2da883eadf663b9b485fe1c14f02883541fa4e6c16f7be8c74";
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockHash).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockHash The block hash. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, String blockHash) { + return new Request<>( + "klay_createAccessList", + Arrays.asList(callObject, blockHash), + web3jService, + AccessListResult.class + ); + } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
+     * {@code
+     *
+     * long blockNumber = 5;
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockNumber The block number. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, long blockNumber) { + return new Request<>( + "klay_createAccessList", + Arrays.asList(callObject, blockNumber), + web3jService, + AccessListResult.class + ); + } + /** * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. *
Example :
      * {@code
-     *  long blockCount = 5;
-     *  // Use block number from Transaction Receipt data.
-     *  long lastBlock = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue();
-     *  List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
-     *  FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, lastBlock, rewardPercentiles).send();
+     *
+     * long blockCount = 5;
+     * // Use block number from Transaction Receipt data.
+     * long lastBlock = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue();
+     * List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     * FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, lastBlock, rewardPercentiles).send();
+     *
      * }
      * 
* @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. @@ -1467,9 +1548,11 @@ public Request getFeeHistory(long blockCount, long lastBlock, Lis * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. *
Example :
      * {@code
-     *  long blockCount = 5;
-     *  List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
-     *  FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send();
+     *
+     * long blockCount = 5;
+     * List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     * FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send();
+     *
      * }
      * 
* @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java new file mode 100644 index 000000000..6723308a0 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java @@ -0,0 +1,138 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.type; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; +import org.web3j.rlp.RlpType; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * AccessTuple is a class representing an element of EIP-2930 access list. + */ +@JsonDeserialize(using = AccessTuple.AccessTupleDeserializer.class) +public class AccessTuple { + private String address; + private List storageKeys; + + public AccessTuple(String address, List storageKeys) { + this.address = address; + this.storageKeys = storageKeys; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getStorageKeys() { + return storageKeys; + } + + public void setStorageKeys(List storageKeys) { + this.storageKeys = storageKeys; + } + + public static AccessTuple decode(RlpList rlpEncodedAccessTuple) { + try { + List accessTupleRlp = rlpEncodedAccessTuple.getValues(); + if (accessTupleRlp.size() <= 0) { + return null; + } + String address = ((RlpString) accessTupleRlp.get(0)).asString(); + List storageKeys = new ArrayList<>(); + List storageKeysRlpType = ((RlpList) (accessTupleRlp.get(1))).getValues(); + for (RlpType storageKeyRlpType : storageKeysRlpType) { + storageKeys.add(((RlpString) storageKeyRlpType).asString()); + } + return new AccessTuple(address, storageKeys); + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + + /** + * Returns the RLP-encoded string of this accessTuple. + * + * @return RlpList + */ + public RlpList toRlpList() { + List storageKeysRLPList = new ArrayList<>(); + for (String storageKey : getStorageKeys()) { + storageKeysRLPList.add(RlpString.create(Numeric.hexStringToByteArray(storageKey))); + } + return new RlpList( + RlpString.create(Numeric.hexStringToByteArray(getAddress())), + new RlpList(storageKeysRLPList) + ); + } + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param o The reference object with which to compare. + * @return boolean + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AccessTuple that = (AccessTuple) o; + + if (!address.equals(that.address)) { + return false; + } + if (!storageKeys.equals(that.storageKeys)) { + return false; + } + return true; + } + + public static class AccessTupleDeserializer extends JsonDeserializer { + @Override + public AccessTuple deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode root = p.getCodec().readTree(p); + JsonNode addressNode = root.get("address"); + String address = (addressNode == null ? "" : addressNode.textValue()); + List storageKeys = new ArrayList<>(); + Iterator storageKeysNodeIterator = root.get("storageKeys").iterator(); + while (storageKeysNodeIterator.hasNext()) { + storageKeys.add(storageKeysNodeIterator.next().textValue()); + } + return new AccessTuple(address, storageKeys); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index 68db9ec15..5daee8c99 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -970,6 +970,37 @@ public void getFeeHistoryTest() throws IOException, InterruptedException { checkFeeHistoryResult(feeHistory, blockCount, rewardPercentiles); } + @Test + public void createAccessListTest() throws IOException { + Block block = caver.rpc.klay.getBlockByNumber(DefaultBlockParameterName.LATEST).send(); + Quantity gasPrice = caver.rpc.klay.getGasPrice().send(); + long blockNumber = new BigInteger(caver.utils.stripHexPrefix(block.getResult().getNumber()), 16).longValue(); + String blockHash = block.getResult().getHash(); + CallObject callObject = CallObject.createCallObject( + LUMAN.getAddress(), + WAYNE.getAddress(), + BigInteger.valueOf(100000), + gasPrice.getValue(), + BigInteger.valueOf(1) + ); + AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send(); + checkAccessListResult(accessListResult.getResult()); + + accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send(); + checkAccessListResult(accessListResult.getResult()); + + accessListResult = caver.rpc.klay.createAccessList(callObject, blockHash).send(); + checkAccessListResult(accessListResult.getResult()); + } + + // checkAccessListResult checks whether given AcccessListResultData instance is right or not. + private void checkAccessListResult(AccessListResult.AccessListResultData accessListResultData) { + + assertEquals(0, accessListResultData.getAccessList().size()); // For now Klaytn will return empty access list. + assertEquals("0x0", accessListResultData.getGasUsed()); // For now Klaytn will return zero gasUsed. + assertEquals(null, accessListResultData.getError()); + } + @Test public void isParallelDbWriteTest() throws Exception { Boolean response = caver.rpc.klay.isParallelDBWrite().send(); From 5f3be0ac7218e7ff9ae40247b714bd3a4423400b Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 02:27:40 +0900 Subject: [PATCH 08/89] Fix failed test cases --- core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index 5daee8c99..a0109a6a6 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -800,7 +800,7 @@ public void estimateGasTest() throws Exception { ); Quantity response = caver.rpc.klay.estimateGas(callObject).send(); String result = response.getResult(); - assertEquals("0xb2d9", result); + assertEquals("0xcb09", result); } @Test @@ -817,7 +817,7 @@ public void estimateComputationCostTest() throws Exception { ); Quantity response = caver.rpc.klay.estimateComputationCost(callObject, DefaultBlockParameterName.LATEST).send(); String result = response.getResult(); - assertEquals("0xe036", result); + assertEquals("0xd949", result); } @Test From 203ab3dff273cef3d6a9ed5fd0c0d93a83f633b8 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 02:33:29 +0900 Subject: [PATCH 09/89] Rename FeeHistory to FeeHistoryResult --- .../{FeeHistory.java => FeeHistoryResult.java} | 6 +++--- .../main/java/com/klaytn/caver/rpc/Klay.java | 8 ++++---- .../com/klaytn/caver/common/rpc/RpcTest.java | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) rename core/src/main/java/com/klaytn/caver/methods/response/{FeeHistory.java => FeeHistoryResult.java} (92%) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java similarity index 92% rename from core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java rename to core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java index 68dd544c4..082e624ce 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistory.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java @@ -20,14 +20,14 @@ import java.util.List; -public class FeeHistory extends Response { +public class FeeHistoryResult extends Response { @Override - public void setResult(FeeHistory.FeeHistoryData result) { + public void setResult(FeeHistoryResultData result) { super.setResult(result); } - public static class FeeHistoryData { + public static class FeeHistoryResultData { /** * Lowest number block of returned range. */ diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index 7491602fa..aae1e7b28 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -1536,12 +1536,12 @@ public Request createAccessList(CallObject callObject, long * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) * @return */ - public Request getFeeHistory(long blockCount, long lastBlock, List rewardPercentiles) { + public Request getFeeHistory(long blockCount, long lastBlock, List rewardPercentiles) { return new Request<>( "klay_feeHistory", Arrays.asList(blockCount, lastBlock, rewardPercentiles), web3jService, - FeeHistory.class); + FeeHistoryResult.class); } /** @@ -1560,12 +1560,12 @@ public Request getFeeHistory(long blockCount, long lastBlock, Lis * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) * @return */ - public Request getFeeHistory(long blockCount, DefaultBlockParameter lastBlock, List rewardPercentiles) { + public Request getFeeHistory(long blockCount, DefaultBlockParameter lastBlock, List rewardPercentiles) { return new Request<>( "klay_feeHistory", Arrays.asList(blockCount, lastBlock, rewardPercentiles), web3jService, - FeeHistory.class); + FeeHistoryResult.class); } /** diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index a0109a6a6..f4dc21278 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -899,11 +899,11 @@ public void getMaxPriorityFeePerGasTest() throws Exception { } // checkFeeHistoryResult checks response from getFeeHistory is ok or not. - private void checkFeeHistoryResult(FeeHistory feeHistory, long blockCount, List rewardPercentiles) { - FeeHistory.FeeHistoryData feeHistoryData = feeHistory.getResult(); - assertEquals(blockCount + 1, feeHistoryData.getBaseFeePerGas().size()); + private void checkFeeHistoryResult(FeeHistoryResult feeHistoryResult, long blockCount, List rewardPercentiles) { + FeeHistoryResult.FeeHistoryResultData feeHistoryResultData = feeHistoryResult.getResult(); + assertEquals(blockCount + 1, feeHistoryResultData.getBaseFeePerGas().size()); - List> reward = feeHistoryData.getReward(); + List> reward = feeHistoryResultData.getReward(); if (reward != null) { assertEquals(blockCount, reward.size()); Consumer> consumer = rewardElement -> { @@ -916,7 +916,7 @@ private void checkFeeHistoryResult(FeeHistory feeHistory, long blockCount, List< reward.forEach(consumer); } - assertEquals(blockCount, feeHistoryData.getGasUsedRatio().size()); + assertEquals(blockCount, feeHistoryResultData.getGasUsedRatio().size()); } @Test @@ -961,13 +961,13 @@ public void getFeeHistoryTest() throws IOException, InterruptedException { long blockCount = 5; long blockNumber = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue(); List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f)); - FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, blockNumber, rewardPercentiles).send(); - checkFeeHistoryResult(feeHistory, blockCount, rewardPercentiles); + FeeHistoryResult feeHistoryResult = caver.rpc.klay.getFeeHistory(blockCount, blockNumber, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistoryResult, blockCount, rewardPercentiles); blockCount = 5; rewardPercentiles = null; - feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send(); - checkFeeHistoryResult(feeHistory, blockCount, rewardPercentiles); + feeHistoryResult = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistoryResult, blockCount, rewardPercentiles); } @Test From f025779b91fcf952bfd4ab6a611597f6f3dc663a Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 02:38:44 +0900 Subject: [PATCH 10/89] Add voteData as a field of BlockHeader --- .../caver/methods/response/BlockHeader.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java index 92148398b..a57abd9c0 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java @@ -110,10 +110,15 @@ public static class BlockHeaderData { */ private String baseFeePerGas; + /** + * RLP encoded governance vote of the proposer + */ + private String voteData; + public BlockHeaderData() { } - public BlockHeaderData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String extraData, String gasUsed, String timestamp, String timestampFoS, String governanceData, String baseFeePerGas) { + public BlockHeaderData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String extraData, String gasUsed, String timestamp, String timestampFoS, String governanceData, String baseFeePerGas, String voteData) { this.number = number; this.hash = hash; this.parentHash = parentHash; @@ -129,6 +134,7 @@ public BlockHeaderData(String number, String hash, String parentHash, String log this.timestampFoS = timestampFoS; this.governanceData = governanceData; this.baseFeePerGas = baseFeePerGas; + this.voteData = voteData; } public String getNumber() { @@ -250,5 +256,13 @@ public String getBaseFeePerGas() { public void setBaseFeePerGas(String baseFeePerGas) { this.baseFeePerGas = baseFeePerGas; } + + public String getVoteData() { + return voteData; + } + + public void setVoteData(String voteData) { + this.voteData = voteData; + } } } From 790503652b26061a3b4c50d885505f0ac6bf81ce Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 03:28:36 +0900 Subject: [PATCH 11/89] Draft: Implement EthereumAccessList Test cases will be added more later. --- .../transaction/AbstractTransaction.java | 5 +- .../caver/transaction/TransactionDecoder.java | 2 + .../caver/transaction/TxPropertyBuilder.java | 8 + .../transaction/type/EthereumAccessList.java | 436 ++++++++++++++ .../transaction/type/TransactionType.java | 4 +- .../wrapper/EthereumAccessListWrapper.java | 112 ++++ .../wrapper/TransactionWrapper.java | 6 + .../transaction/EthereumAccessListTest.java | 531 ++++++++++++++++++ 8 files changed, 1101 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java create mode 100644 core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index e8390837f..ba7658e96 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.AccountKeyRoleBased; +import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.LegacyTransaction; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.utils.Utils; @@ -647,8 +648,8 @@ public void setType(String type) { } public void setFrom(String from) { - //"From" field in LegacyTransaction allows null - if(this instanceof LegacyTransaction) { + //"From" field in LegacyTransaction or EthereumAccessList allows null + if(this instanceof LegacyTransaction || this instanceof EthereumAccessList) { if(from == null || from.isEmpty() || from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS)) from = Utils.DEFAULT_ZERO_ADDRESS; } else { if(from == null) { diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java index c4e2dd116..30c244b51 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java @@ -70,6 +70,8 @@ public static AbstractTransaction decode(String rlpEncoded) { return FeeDelegatedValueTransferMemoWithRatio.decode(rlpBytes); } else if(rlpBytes[0] == TransactionType.TxTypeFeeDelegatedSmartContractDeployWithRatio.getType()) { return FeeDelegatedSmartContractDeployWithRatio.decode(rlpBytes); + } else if ((rlpBytes[0] << 8 | rlpBytes[1]) == TransactionType.TxTypeEthereumAccessList.getType()) { + return EthereumAccessList.decode(rlpBytes); } else { return LegacyTransaction.decode(rlpBytes); diff --git a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java index 8ea620422..b3b0fdb7e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java @@ -30,6 +30,14 @@ public static LegacyTransaction.Builder legacyTransaction() { return new LegacyTransaction.Builder(); } + /** + * Creates a Builder of EthereumAccessList + * @return EthereumAccessList.Builder + */ + public static EthereumAccessList.Builder ethereumAccessList() { + return new EthereumAccessList.Builder(); + } + /** * Creates a Builder of ValueTransfer * @return ValueTransfer.Builder diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java new file mode 100644 index 000000000..4d2de23a9 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -0,0 +1,436 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.type; + +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents an ethereum access list transaction. + */ +public class EthereumAccessList extends AbstractTransaction { + /** + * The account address that will receive the transferred value. + */ + String to = "0x"; + + /** + * Data attached to the transaction, used for transaction execution. + */ + String input = "0x"; + + /** + * The amount of KLAY in peb to be transferred. + */ + String value = "0x00"; + + /** + * Access list is an EIP-2930 access list. + */ + List accessList; + + /** + * EthereumAccessList Builder class + */ + public static class Builder extends AbstractTransaction.Builder { + private String to = "0x"; + private String value = "0x0"; + private String input = "0x"; + private List accessList = new ArrayList<>(); + + public Builder() { + super(TransactionType.TxTypeEthereumAccessList.toString()); + } + + public EthereumAccessList.Builder setValue(String value) { + this.value = value; + return this; + } + + public EthereumAccessList.Builder setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + return this; + } + + public EthereumAccessList.Builder setInput(String input) { + this.input = input; + return this; + } + + public EthereumAccessList.Builder setTo(String to) { + this.to = to; + return this; + } + + public EthereumAccessList.Builder setAccessList(List accessList) { + this.accessList = accessList; + return this; + } + + public EthereumAccessList build() { + return new EthereumAccessList(this); + } + } + + /** + * Creates a EthereumAccessList instance. + * + * @param builder EthereumAccessList.Builder instance. + * @return EthereumAccessList + */ + public static EthereumAccessList create(EthereumAccessList.Builder builder) { + return new EthereumAccessList(builder); + } + + + /** + * Create a EthereumAccessList instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumAccessList + */ + public static EthereumAccessList create(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { + return new EthereumAccessList(klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); + } + + /** + * Creates a EthereumAccessList instance. + * + * @param builder EthereumAccessList.Builder instance. + */ + public EthereumAccessList(EthereumAccessList.Builder builder) { + super(builder); + + setTo(builder.to); + setValue(builder.value); + setInput(builder.input); + setAccessList(builder.accessList); + } + + + /** + * Create a EthereumAccessList instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + */ + public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { + super( + klaytnCall, + TransactionType.TxTypeLegacyTransaction.toString(), + from, + nonce, + gas, + gasPrice, + chainId, + signatures + ); + setTo(to); + setValue(value); + setInput(input); + setAccessList(accessList); + } + + @Override + public String getRLPEncoding() { + // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGasPrice()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + + List accessListRLP = new ArrayList<>(); + for (AccessTuple accessTuple : this.getAccessList()) { + accessListRLP.add(accessTuple.toRlpList()); + } + rlpTypeList.add(new RlpList(accessListRLP)); + SignatureData signatureData = this.getSignatures().get(0); + rlpTypeList.addAll(signatureData.toRlpList().getValues()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = Numeric.toBytesPadded(BigInteger.valueOf(TransactionType.TxTypeEthereumAccessList.getType()), 2); + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + @Override + public String getCommonRLPEncodingForSignature() { + return getRLPEncodingForSignature(); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * + * @return String + */ + @Override + public String getRLPEncodingForSignature() { + // SigRLP = 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]) + + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGasPrice()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + + List accessListRLP = new ArrayList<>(); + for (AccessTuple accessTuple : this.getAccessList()) { + accessListRLP.add(accessTuple.toRlpList()); + } + rlpTypeList.add(new RlpList(accessListRLP)); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = new byte[]{(byte) TransactionType.TxTypeEthereumAccessList.getType()}; + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Check equals txObj passed parameter and Current instance. + * + * @param obj The AbstractTransaction Object to compare + * @param checkSig Check whether signatures field is equal. + * @return boolean + */ + @Override + public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { + if (!super.compareTxField(obj, checkSig)) return false; + if (!(obj instanceof EthereumAccessList)) return false; + EthereumAccessList txObj = (EthereumAccessList) obj; + + if (!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; + if (!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getInput().equals(txObj.getInput())) return false; + if (!this.getAccessList().equals(txObj.getAccessList())) return false; + + return true; + } + + + /** + * Decodes a RLP-encoded EthereumAccessList string. + * + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public static EthereumAccessList decode(String rlpEncoded) { + return decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Decodes a RLP-encoded EthereumAccessList byte array. + * + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public static EthereumAccessList decode(byte[] rlpEncoded) { + // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + try { + if ((rlpEncoded[0] << 8 | rlpEncoded[1]) != TransactionType.TxTypeEthereumAccessList.getType()) { + throw new IllegalArgumentException("Invalid RLP-encoded tag - " + TransactionType.TxTypeEthereumAccessList.toString()); + } + byte[] detachedType = Arrays.copyOfRange(rlpEncoded, 2, rlpEncoded.length); + RlpList rlpList = RlpDecoder.decode(detachedType); + List values = ((RlpList) rlpList.getValues().get(0)).getValues(); + + BigInteger chainId = ((RlpString) values.get(0)).asPositiveBigInteger(); + BigInteger nonce = ((RlpString) values.get(1)).asPositiveBigInteger(); + BigInteger gasPrice = ((RlpString) values.get(2)).asPositiveBigInteger(); + BigInteger gas = ((RlpString) values.get(3)).asPositiveBigInteger(); + String to = ((RlpString) values.get(4)).asString(); + BigInteger value = ((RlpString) values.get(5)).asPositiveBigInteger(); + String input = ((RlpString) values.get(6)).asString(); + + List accessList = new ArrayList<>(); + List accessListRlpType = ((RlpList) (values.get(7))).getValues(); + for (RlpType accessTupleRlpType : accessListRlpType) { + accessList.add(AccessTuple.decode((RlpList) accessTupleRlpType)); + } + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setFrom(null) + .setInput(input) + .setValue(value) + .setChainId(chainId) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setNonce(nonce) + .setTo(to) + .setAccessList(accessList) + .build(); + + byte[] v = ((RlpString) values.get(8)).getBytes(); + byte[] r = ((RlpString) values.get(9)).getBytes(); + byte[] s = ((RlpString) values.get(10)).getBytes(); + SignatureData signatureData = new SignatureData(v, r, s); + + ethereumAccessList.appendSignatures(signatureData); + return ethereumAccessList; + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + /** + * Getter function for to + * + * @return String + */ + public String getTo() { + return to; + } + + /** + * Setter function for to + * + * @param to The account address that will receive the transferred value. + */ + public void setTo(String to) { + // "to" field in EthereumAccessList allows null + if (to == null || to.isEmpty()) { + to = "0x"; + } + + if (!to.equals("0x") && !Utils.isAddress(to)) { + throw new IllegalArgumentException("Invalid address. : " + to); + } + this.to = to; + } + + /** + * Getter function for input + * + * @return String + */ + public String getInput() { + return input; + } + + /** + * Setter function for input + * + * @param input Data attached to the transaction. + */ + public void setInput(String input) { + if (input == null) { + throw new IllegalArgumentException("input is missing"); + } + + if (!Utils.isHex(input)) { + throw new IllegalArgumentException("Invalid input : " + input); + } + this.input = Numeric.prependHexPrefix(input); + } + + + /** + * Getter function for value + * + * @return String + */ + public String getValue() { + return value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(String value) { + if (value == null) { + throw new IllegalArgumentException("value is missing"); + } + + if (!Utils.isNumber(value)) { + throw new IllegalArgumentException("Invalid value : " + value); + } + this.value = value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + } + + /** + * Getter function for accessList + * + * @return accessList Access list is an EIP-2930 access list. + */ + public List getAccessList() { + return accessList; + } + + /** + * Setter function for accessList + * + * @param accessList Access list is an EIP-2930 access list. + */ + public void setAccessList(List accessList) { + if (accessList == null) { + accessList = new ArrayList<>(); + } + this.accessList = accessList; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 0ec21ae9a..09a5ffaac 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -45,7 +45,9 @@ public enum TransactionType { TxTypeChainDataAnchoring(0x48), TxTypeFeeDelegatedChainDataAnchoring(0x49), - TxTypeFeeDelegatedChainDataAnchoringWithRatio(0x4a); + TxTypeFeeDelegatedChainDataAnchoringWithRatio(0x4a), + + TxTypeEthereumAccessList(0x7801); int type; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java new file mode 100644 index 000000000..ef0803f6a --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -0,0 +1,112 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.type.wrapper; + +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.wallet.keyring.SignatureData; + +import java.util.List; + +/** + * Represents a EthereumAccessListWrapper + * 1. This class wraps all of static methods of EthereumAccessList + * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + */ +public class EthereumAccessListWrapper { + /** + * Klay RPC instance + */ + private Klay klaytnCall; + + /** + * Creates a EthereumAccessListWrapper instance. + * @param klaytnCall Klay RPC instance + */ + public EthereumAccessListWrapper(Klay klaytnCall) { + this.klaytnCall = klaytnCall; + } + + /** + * Creates a EthereumAccessList instance derived from a RLP-encoded EthereumAccessList string. + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public EthereumAccessList create(String rlpEncoded) { + EthereumAccessList legacyTransaction = EthereumAccessList.decode(rlpEncoded); + legacyTransaction.setKlaytnCall(this.klaytnCall); + return legacyTransaction; + } + + /** + * Creates a EthereumAccessList instance derived from a RLP-encoded EthereumAccessList byte array. + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public EthereumAccessList create(byte[] rlpEncoded) { + EthereumAccessList legacyTransaction = EthereumAccessList.decode(rlpEncoded); + legacyTransaction.setKlaytnCall(this.klaytnCall); + return legacyTransaction; + } + + /** + * Creates a EthereumAccessList instance using EthereumAccessList.Builder + * @param builder EthereumAccessList.Builder + * @return EthereumAccessList + */ + public EthereumAccessList create(EthereumAccessList.Builder builder) { + builder.setKlaytnCall(this.klaytnCall); + return EthereumAccessList.create(builder); + } + + /** + * Create a EthereumAccessList instance. + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumAccessList + */ + public EthereumAccessList create(String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { + return EthereumAccessList.create(klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); + } + + /** + * Decodes a RLP-encoded EthereumAccessList string. + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public EthereumAccessList decode(String rlpEncoded) { + return EthereumAccessList.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded EthereumAccessList byte array. + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public EthereumAccessList decode(byte[] rlpEncoded) { + return EthereumAccessList.decode(rlpEncoded); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 52e73b1d3..47a14b8b5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -38,6 +38,11 @@ public class TransactionWrapper { */ public LegacyTransactionWrapper legacyTransaction; + /** + * EthereumAccessListWrapper instance + */ + public EthereumAccessListWrapper ethereumAccessList; + /** * ValueTransferWrapper instance */ @@ -151,6 +156,7 @@ public TransactionWrapper(Klay klaytnCall) { this.klay = klaytnCall; this.legacyTransaction = new LegacyTransactionWrapper(klaytnCall); + this.ethereumAccessList = new EthereumAccessListWrapper(klaytnCall); this.valueTransfer = new ValueTransferWrapper(klaytnCall); this.feeDelegatedValueTransfer = new FeeDelegatedValueTransferWrapper(klaytnCall); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java new file mode 100644 index 000000000..e44a4ea23 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -0,0 +1,531 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction; + +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.TransactionDecoder; +import com.klaytn.caver.transaction.TxPropertyBuilder; +import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(Enclosed.class) +public class EthereumAccessListTest { + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + String nonce = "0x4D2"; + String gas = "0xf4240"; + String gasPrice = "0x19"; + String from = "0x"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + List accessList = new ArrayList<>( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void createInstance() throws IOException { + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(keyring.getAddress()) + .setNonce("0x") + .setGas(gas) + .setGasPrice("0x") + .setChainId("0x") + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + + assertNotNull(ethereumAccessList); + + ethereumAccessList.fillTransaction(); + assertNotNull(ethereumAccessList.getNonce()); + assertNotNull(ethereumAccessList.getGasPrice()); + assertNotNull(ethereumAccessList.getChainId()); + assertEquals(accessList, ethereumAccessList.getAccessList()); + assertEquals(TransactionType.TxTypeEthereumAccessList.toString(), ethereumAccessList.getType()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + String value = "invalid"; + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String to = "invalid"; + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + } + + public static class createInstanceBuilder { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + static String nonce = "0x4D2"; + static String gas = "0xf4240"; + static String gasPrice = "0x19"; + static String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + static String chainID = "0x1"; + static String input = "0x31323334"; + static String value = "0xa"; + List accessList = new ArrayList<>( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + @Test + public void BuilderTest() { + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertNotNull(ethereumAccessList); + assertEquals(TransactionType.TxTypeEthereumAccessList.toString(), ethereumAccessList.getType()); + } + + @Test + public void BuilderTestWithBigInteger() { + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(Numeric.toBigInt(gasPrice)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertEquals(gas, ethereumAccessList.getGas()); + assertEquals(gasPrice, ethereumAccessList.getGasPrice()); + assertEquals(chainID, ethereumAccessList.getChainId()); + assertEquals(value, ethereumAccessList.getValue()); + assertEquals(accessList, ethereumAccessList.getAccessList()); + } + + @Test + public void BuilderTestRPC() throws IOException { + String gas = "0x0f4240"; + String to = "7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0x0a"; + + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setKlaytnCall(caver.rpc.getKlay()) + .setGas(gas) + .setInput(input) + .setValue(value) + .setFrom(keyring.getAddress()) // For Test. It automatically filled when executed EthereumAccessList.signWithKey or signWithKeysTest. + .setTo(to) + .setAccessList(accessList) + .build(); + + ethereumAccessList.fillTransaction(); + assertNotNull(ethereumAccessList.getNonce()); + assertNotNull(ethereumAccessList.getGasPrice()); + assertNotNull(ethereumAccessList.getChainId()); + assertNotNull(ethereumAccessList.getAccessList()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("0x") + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_invalid_value2() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("invalid") + .build(); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String gas = "0xf4240"; + String to = "invalid"; + String input = "0x31323334"; + String value = "0xa"; + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + } + + public static class getRLPEncodingAndDecodingTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void getRLPEncodingAndDecoding() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + List accessList1 = new ArrayList<>(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList("0x0000000000000000000000000000000000000000000000000000000000000000")) + )); + List accessList2 = new ArrayList<>(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + SignatureData signatureData1 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293"), + Numeric.hexStringToByteArray("0x1d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f") + ); + SignatureData signatureData2 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + SignatureData signatureData3 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x3966e6b3297cbcfdbb3e1e3ede1b80dea99d9d01983fc32e337d785fe4445973"), + Numeric.hexStringToByteArray("0x49e25fcda7c8d5517181e2afcf13d2dff77dc33884b512fd1f1cd0770bcd0c59") + ); + + // Test-1: encoding EthereumAccessList which have an address and a storage key. + String expectedRLP ="0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce("0x4") + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList1) + .setSignatures(signatureData1) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-1: decoding + EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-2: encoding EthereumAccessList which have many storageKeys + expectedRLP = "0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(BigInteger.valueOf(4)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList2) + .setSignatures(signatureData2) + ); + expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-2: decoding + decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-3: encoding EthereumAccessList which have empty accessList + expectedRLP = "0x7801f86801030a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566c001a03966e6b3297cbcfdbb3e1e3ede1b80dea99d9d01983fc32e337d785fe4445973a049e25fcda7c8d5517181e2afcf13d2dff77dc33884b512fd1f1cd0770bcd0c59"; + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(BigInteger.valueOf(3)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(null) + .setInput(input) + .setSignatures(signatureData3) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-2: decoding + decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + } + + @Test + public void throwException_NoNonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + String gas = "0xf4240"; + String gasPrice = "0x19"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + @Test + public void throwException_NoGasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + @Test + public void throwException_NoChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String gasPrice = "0x10"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + + } + +} From 32de5ea0bbda1c3d133ec9a90375da6df20ef0c9 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 09:13:34 +0900 Subject: [PATCH 12/89] Change package structure --- .../com/klaytn/caver/methods/response/AccessListResult.java | 2 +- .../com/klaytn/caver/transaction/type/EthereumAccessList.java | 1 + .../transaction/type/{ => transactionUtils}/AccessTuple.java | 2 +- .../transaction/type/wrapper/EthereumAccessListWrapper.java | 2 +- .../klaytn/caver/common/transaction/EthereumAccessListTest.java | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) rename core/src/main/java/com/klaytn/caver/transaction/type/{ => transactionUtils}/AccessTuple.java (98%) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java index 5d9cf6ab2..3ed6431da 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -22,7 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; import org.web3j.protocol.core.Response; import java.io.IOException; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 4d2de23a9..857075cd3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -18,6 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java similarity index 98% rename from core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java rename to core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java index 6723308a0..2a8f8b99c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.type; +package com.klaytn.caver.transaction.type.transactionUtils; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java index ef0803f6a..e1b01fc2a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -17,7 +17,7 @@ package com.klaytn.caver.transaction.type.wrapper; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index e44a4ea23..d40c32ddc 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -19,7 +19,7 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TxPropertyBuilder; -import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.wallet.keyring.AbstractKeyring; From fef6b05e34a82faeacc1b12a313526d5290c2adc Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 16:56:58 +0900 Subject: [PATCH 13/89] Add AccessList class Add class for EIP-2930 access list. --- .../methods/response/AccessListResult.java | 15 +- .../transaction/accessList/AccessList.java | 95 ++++++++++ .../AccessTuple.java | 46 ++++- .../accessList/wrapper/AccessListWrapper.java | 77 +++++++++ .../transaction/type/EthereumAccessList.java | 38 ++-- .../wrapper/EthereumAccessListWrapper.java | 6 +- .../wrapper/TransactionWrapper.java | 8 + .../transaction/EthereumAccessListTest.java | 57 ++++-- .../accessList/AccessListTest.java | 163 ++++++++++++++++++ 9 files changed, 452 insertions(+), 53 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java rename core/src/main/java/com/klaytn/caver/transaction/{type/transactionUtils => accessList}/AccessTuple.java (83%) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java index 3ed6431da..f01e50d21 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -22,12 +22,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; +import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.accessList.AccessTuple; import org.web3j.protocol.core.Response; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; public class AccessListResult extends Response { @@ -38,21 +37,21 @@ public void setResult(AccessListResultData result) { @JsonDeserialize(using = AccessListResultData.AccessListResultDeserializer.class) public static class AccessListResultData { - List accessList; + AccessList accessList; String error; String gasUsed; - public AccessListResultData(List accessList, String error, String gasUsed) { + public AccessListResultData(AccessList accessList, String error, String gasUsed) { this.accessList = accessList; this.error = error; this.gasUsed = gasUsed; } - public List getAccessList() { + public AccessList getAccessList() { return accessList; } - public void setAccessList(List accessList) { + public void setAccessList(AccessList accessList) { this.accessList = accessList; } @@ -78,7 +77,7 @@ public AccessListResultData deserialize(JsonParser p, DeserializationContext ctx ObjectMapper objectMapper = new ObjectMapper(); JsonNode node = p.getCodec().readTree(p); - List accessList = new ArrayList<>(); + AccessList accessList = new AccessList(); JsonNode accessListNode = node.get("accessList"); for (JsonNode innerNode : accessListNode) { AccessTuple accessTuple = objectMapper.treeToValue(innerNode, AccessTuple.class); diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java new file mode 100644 index 000000000..76e9cc7a0 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java @@ -0,0 +1,95 @@ +package com.klaytn.caver.transaction.accessList; + +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.util.*; + +/** + * Represents an access list. + * AccessList is an EIP-2930 access list. + */ +public class AccessList extends ArrayList { + /** + * Constructor. + */ + public AccessList() {} + + /** + * Creates an AccessList instance from given list of AccessTuple. + * @param items A list of AccessTuple. + */ + public AccessList(List items) { + this.addAll(items); + } + + /** + * Creates an Access list. + * + * @param items + * @return + */ + public static AccessList create(List items) { + AccessList accessList = new AccessList(items); + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param accessListRlpList List of RlpType to decode. + * @return AccessList + */ + public static AccessList decode(RlpList accessListRlpList) { + List accessTupleRlps = accessListRlpList.getValues(); + AccessList accessList = new AccessList(); + for (int i =0 ; i < accessTupleRlps.size(); i++) { + accessList.add(AccessTuple.decode((RlpList) accessTupleRlps.get(i))); + } + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded string of access list. + * @return + */ + public static AccessList decode(String rlpEncoded) { + return AccessList.decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded byte array of access list. + * @return + */ + public static AccessList decode(byte[] rlpEncoded) { + RlpList rlpList = RlpDecoder.decode(rlpEncoded); + return AccessList.decode(((RlpList) rlpList.getValues().get(0))); + } + + /** + * Returns the RLP-encoded string of this accessList. + * + * @return RlpList + */ + public RlpList toRlpList() { + List rlpTypeList = new ArrayList<>(); + for (AccessTuple accessTuple : this) { + rlpTypeList.add(accessTuple.toRlpList()); + } + return new RlpList(rlpTypeList); + } + + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java similarity index 83% rename from core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java rename to core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java index 2a8f8b99c..6989da77d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/transactionUtils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.type.transactionUtils; +package com.klaytn.caver.transaction.accessList; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.web3j.rlp.RlpEncoder; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; import org.web3j.rlp.RlpType; @@ -39,39 +40,61 @@ public class AccessTuple { private String address; private List storageKeys; + /** + * Create an AccessTuple instance. + * @param address An address string. + * @param storageKeys A list of storage keys. + */ public AccessTuple(String address, List storageKeys) { this.address = address; this.storageKeys = storageKeys; } + /** + * Getter function of address. + * @return String + */ public String getAddress() { return address; } + /** + * Setter function of address. + * @param address + */ public void setAddress(String address) { this.address = address; } + /** + * Getter function of storageKeys. + * @return List<String> + */ public List getStorageKeys() { return storageKeys; } + /** + * Setter function of storageKeys. + * @param storageKeys A list of storage keys. + */ public void setStorageKeys(List storageKeys) { this.storageKeys = storageKeys; } + /** + * Decodes given RlpList to AccessTuple. + * @param rlpEncodedAccessTuple + * @return + */ public static AccessTuple decode(RlpList rlpEncodedAccessTuple) { try { List accessTupleRlp = rlpEncodedAccessTuple.getValues(); - if (accessTupleRlp.size() <= 0) { - return null; - } String address = ((RlpString) accessTupleRlp.get(0)).asString(); List storageKeys = new ArrayList<>(); List storageKeysRlpType = ((RlpList) (accessTupleRlp.get(1))).getValues(); for (RlpType storageKeyRlpType : storageKeysRlpType) { - storageKeys.add(((RlpString) storageKeyRlpType).asString()); - } + storageKeys.add(((RlpString) storageKeyRlpType).asString()); } return new AccessTuple(address, storageKeys); } catch (Exception e) { throw new RuntimeException("There is an error while decoding process."); @@ -95,6 +118,17 @@ public RlpList toRlpList() { ); } + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } + + /** * Indicates whether some other object is "equal to" this one. * diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java new file mode 100644 index 000000000..20aa41c60 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.accessList.wrapper; + +import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.accessList.AccessTuple; +import org.web3j.rlp.RlpList; + +import java.util.List; + +/** + * Represents a AccessListWrapper + * 1. This class wraps all of static methods of AccessList + * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + */ +public class AccessListWrapper { + /** + * Creates a AccessListWrapper instance. + */ + public AccessListWrapper() { + } + + /** + * Creates a AccessList instance derived from a RLP-encoded AccessList string. + * + * @param items A list of AccessTuple + * @return AccessList + */ + public AccessList create(List items) { + return AccessList.create(items); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(String rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(byte[] rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + + /** + * Decodes a RLP-encoded AccessList. + * + * @param rlpTypeList List of RlpType to decode. + * @return AccessList + */ + public AccessList decode(RlpList rlpTypeList) { + return AccessList.decode(rlpTypeList); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 857075cd3..2394606d2 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -18,7 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; -import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; +import com.klaytn.caver.transaction.accessList.AccessList; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -52,7 +52,7 @@ public class EthereumAccessList extends AbstractTransaction { /** * Access list is an EIP-2930 access list. */ - List accessList; + AccessList accessList; /** * EthereumAccessList Builder class @@ -61,7 +61,7 @@ public static class Builder extends AbstractTransaction.Builder accessList = new ArrayList<>(); + private AccessList accessList = new AccessList(); public Builder() { super(TransactionType.TxTypeEthereumAccessList.toString()); @@ -87,7 +87,7 @@ public EthereumAccessList.Builder setTo(String to) { return this; } - public EthereumAccessList.Builder setAccessList(List accessList) { + public EthereumAccessList.Builder setAccessList(AccessList accessList) { this.accessList = accessList; return this; } @@ -124,7 +124,7 @@ public static EthereumAccessList create(EthereumAccessList.Builder builder) { * @param accessList The EIP-2930 access list. * @return EthereumAccessList */ - public static EthereumAccessList create(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { + public static EthereumAccessList create(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { return new EthereumAccessList(klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); } @@ -157,7 +157,7 @@ public EthereumAccessList(EthereumAccessList.Builder builder) { * @param input Data attached to the transaction, used for transaction execution. * @param value The amount of KLAY in peb to be transferred. */ - public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { + public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { super( klaytnCall, TransactionType.TxTypeLegacyTransaction.toString(), @@ -187,12 +187,7 @@ public String getRLPEncoding() { rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); - - List accessListRLP = new ArrayList<>(); - for (AccessTuple accessTuple : this.getAccessList()) { - accessListRLP.add(accessTuple.toRlpList()); - } - rlpTypeList.add(new RlpList(accessListRLP)); + rlpTypeList.add(this.getAccessList().toRlpList()); SignatureData signatureData = this.getSignatures().get(0); rlpTypeList.addAll(signatureData.toRlpList().getValues()); @@ -227,12 +222,7 @@ public String getRLPEncodingForSignature() { rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); - - List accessListRLP = new ArrayList<>(); - for (AccessTuple accessTuple : this.getAccessList()) { - accessListRLP.add(accessTuple.toRlpList()); - } - rlpTypeList.add(new RlpList(accessListRLP)); + rlpTypeList.add(this.getAccessList().toRlpList()); byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); byte[] type = new byte[]{(byte) TransactionType.TxTypeEthereumAccessList.getType()}; @@ -297,11 +287,7 @@ public static EthereumAccessList decode(byte[] rlpEncoded) { BigInteger value = ((RlpString) values.get(5)).asPositiveBigInteger(); String input = ((RlpString) values.get(6)).asString(); - List accessList = new ArrayList<>(); - List accessListRlpType = ((RlpList) (values.get(7))).getValues(); - for (RlpType accessTupleRlpType : accessListRlpType) { - accessList.add(AccessTuple.decode((RlpList) accessTupleRlpType)); - } + AccessList accessList = AccessList.decode((RlpList) values.get(7)); EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() .setFrom(null) @@ -419,7 +405,7 @@ public void setValue(BigInteger value) { * * @return accessList Access list is an EIP-2930 access list. */ - public List getAccessList() { + public AccessList getAccessList() { return accessList; } @@ -428,9 +414,9 @@ public List getAccessList() { * * @param accessList Access list is an EIP-2930 access list. */ - public void setAccessList(List accessList) { + public void setAccessList(AccessList accessList) { if (accessList == null) { - accessList = new ArrayList<>(); + accessList = new AccessList(); } this.accessList = accessList; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java index e1b01fc2a..bd55e0918 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -17,7 +17,7 @@ package com.klaytn.caver.transaction.type.wrapper; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; +import com.klaytn.caver.transaction.accessList.AccessList; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -88,8 +88,8 @@ public EthereumAccessList create(EthereumAccessList.Builder builder) { * @param accessList The EIP-2930 access list. * @return EthereumAccessList */ - public EthereumAccessList create(String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, List accessList) { - return EthereumAccessList.create(klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); + public EthereumAccessList create(String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return EthereumAccessList.create(this.klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); } /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 47a14b8b5..2522c0602 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -20,6 +20,7 @@ import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; +import com.klaytn.caver.transaction.accessList.wrapper.AccessListWrapper; import com.klaytn.caver.transaction.type.wrapper.*; import java.util.List; @@ -148,6 +149,11 @@ public class TransactionWrapper { */ public FeeDelegatedChainDataAnchoringWithRatioWrapper feeDelegatedChainDataAnchoringWithRatio; + /** + * AccessListWrapper instance + */ + public AccessListWrapper accessList; + /** * Creates a Transaction instance * @param klaytnCall Klay RPC instance @@ -185,6 +191,8 @@ public TransactionWrapper(Klay klaytnCall) { this.chainDataAnchoring = new ChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); + + this.accessList = new AccessListWrapper(); } /** diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index d40c32ddc..e296cc482 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -19,7 +19,8 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TxPropertyBuilder; -import com.klaytn.caver.transaction.type.transactionUtils.AccessTuple; +import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.accessList.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.wallet.keyring.AbstractKeyring; @@ -55,7 +56,7 @@ public static class createInstance { String chainID = "0x1"; String input = "0x31323334"; String value = "0xa"; - List accessList = new ArrayList<>( + AccessList accessList = new AccessList( Arrays.asList( new AccessTuple( "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", @@ -168,7 +169,7 @@ public static class createInstanceBuilder { static String chainID = "0x1"; static String input = "0x31323334"; static String value = "0xa"; - List accessList = new ArrayList<>( + AccessList accessList = new AccessList( Arrays.asList( new AccessTuple( "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", @@ -329,11 +330,11 @@ public void getRLPEncodingAndDecoding() { String chainID = "0x1"; String input = "0x616263646566"; String value = "0x0"; - List accessList1 = new ArrayList<>(Arrays.asList( + AccessList accessList1 = new AccessList(Arrays.asList( new AccessTuple("0x0000000000000000000000000000000000000001", Arrays.asList("0x0000000000000000000000000000000000000000000000000000000000000000")) )); - List accessList2 = new ArrayList<>(Arrays.asList( + AccessList accessList2 = new AccessList(Arrays.asList( new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", Arrays.asList( "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -341,6 +342,22 @@ public void getRLPEncodingAndDecoding() { "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" )) )); + AccessList accessList3 = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x0000000000000000000000000000000000000002", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); SignatureData signatureData1 = new SignatureData( Numeric.hexStringToByteArray("0x01"), @@ -357,6 +374,11 @@ public void getRLPEncodingAndDecoding() { Numeric.hexStringToByteArray("0x3966e6b3297cbcfdbb3e1e3ede1b80dea99d9d01983fc32e337d785fe4445973"), Numeric.hexStringToByteArray("0x49e25fcda7c8d5517181e2afcf13d2dff77dc33884b512fd1f1cd0770bcd0c59") ); + SignatureData signatureData4 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4"), + Numeric.hexStringToByteArray("0x782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb") + ); // Test-1: encoding EthereumAccessList which have an address and a storage key. String expectedRLP ="0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; @@ -377,9 +399,7 @@ public void getRLPEncodingAndDecoding() { assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-2: encoding EthereumAccessList which have many storageKeys - expectedRLP = "0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; - assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); - + expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; ethereumAccessList = caver.transaction.ethereumAccessList.create( TxPropertyBuilder.ethereumAccessList() .setFrom(null) @@ -391,7 +411,6 @@ public void getRLPEncodingAndDecoding() { .setAccessList(accessList2) .setSignatures(signatureData2) ); - expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); // Test-2: decoding decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); @@ -412,9 +431,27 @@ public void getRLPEncodingAndDecoding() { .setSignatures(signatureData3) ); assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); - // Test-2: decoding + // Test-3: decoding decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-4: encoding EthereumAccessList which have many accessList + expectedRLP = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fcf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce(BigInteger.valueOf(4)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList3) + .setSignatures(signatureData4) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-4: decoding + decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + } @Test diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java new file mode 100644 index 000000000..beb9615ef --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction.accessList; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.accessList.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessListTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + static Caver caver = new Caver(); + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessList accessList = caver.transaction.accessList.create( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ) + ) + ); + } + } + + public static class deserializeTest { + + @Test + public void oneAddressAndOneStorageKey() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void oneAddressAndManyStorageKeys() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void manyAccessList() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + } + + public static class decodeTest { + @Test + public void decodeString() throws IOException { + AccessList decodedAccessList = caver.transaction.accessList.decode(encodedAccessList); + System.out.println(objectToString(decodedAccessList)); + Assert.assertTrue(accessList.equals(decodedAccessList)); + } + } + + public static class encodeTest { + @Test + public void encodeToBytes() { + byte[] encoded = accessList.encodeToBytes(); + String encodedString = Numeric.toHexString(encoded); + + AccessList.decode(encodedString); + Assert.assertEquals(encodedAccessList, encodedString); + } + } +} From 3a05bdb6eab20e1e9ebad68a6655a65899b681c9 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 16:59:22 +0900 Subject: [PATCH 14/89] Add license statement --- .../caver/transaction/accessList/AccessList.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java index 76e9cc7a0..39a6947fd 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.accessList; import org.web3j.rlp.*; From f5319f4082c02720b6d8424f29db11b9d3b7e360 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 17:33:50 +0900 Subject: [PATCH 15/89] Re-structure packages related with accessList and add TransactionUtils class --- .../klaytn/caver/methods/response/AccessListResult.java | 4 ++-- .../caver/transaction/type/EthereumAccessList.java | 2 +- .../type/wrapper/EthereumAccessListWrapper.java | 2 +- .../transaction/{ => utils}/accessList/AccessList.java | 2 +- .../transaction/{ => utils}/accessList/AccessTuple.java | 2 +- .../accessList/wrapper/AccessListWrapper.java | 6 +++--- .../caver/transaction/wrapper/TransactionWrapper.java | 9 +++++---- .../caver/common/transaction/EthereumAccessListTest.java | 4 ++-- .../common/transaction/accessList/AccessListTest.java | 8 ++++---- 9 files changed, 20 insertions(+), 19 deletions(-) rename core/src/main/java/com/klaytn/caver/transaction/{ => utils}/accessList/AccessList.java (98%) rename core/src/main/java/com/klaytn/caver/transaction/{ => utils}/accessList/AccessTuple.java (98%) rename core/src/main/java/com/klaytn/caver/transaction/{ => utils}/accessList/wrapper/AccessListWrapper.java (91%) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java index f01e50d21..effde2e10 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -22,8 +22,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.klaytn.caver.transaction.accessList.AccessList; -import com.klaytn.caver.transaction.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessTuple; import org.web3j.protocol.core.Response; import java.io.IOException; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 2394606d2..017ce0677 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -18,7 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; -import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessList; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java index bd55e0918..121a6ae72 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -17,7 +17,7 @@ package com.klaytn.caver.transaction.type.wrapper; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessList; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java similarity index 98% rename from core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java index 39a6947fd..26dab31fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.accessList; +package com.klaytn.caver.transaction.utils.accessList; import org.web3j.rlp.*; import org.web3j.utils.Numeric; diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java similarity index 98% rename from core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java index 6989da77d..56bbc3848 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/accessList/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.accessList; +package com.klaytn.caver.transaction.utils.accessList; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; diff --git a/core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java similarity index 91% rename from core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java index 20aa41c60..f605a8330 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/accessList/wrapper/AccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.accessList.wrapper; +package com.klaytn.caver.transaction.utils.accessList.wrapper; -import com.klaytn.caver.transaction.accessList.AccessList; -import com.klaytn.caver.transaction.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessTuple; import org.web3j.rlp.RlpList; import java.util.List; diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 2522c0602..459f5ccc9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -20,7 +20,8 @@ import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; -import com.klaytn.caver.transaction.accessList.wrapper.AccessListWrapper; +import com.klaytn.caver.transaction.utils.TransactionUtils; +import com.klaytn.caver.transaction.utils.accessList.wrapper.AccessListWrapper; import com.klaytn.caver.transaction.type.wrapper.*; import java.util.List; @@ -150,9 +151,9 @@ public class TransactionWrapper { public FeeDelegatedChainDataAnchoringWithRatioWrapper feeDelegatedChainDataAnchoringWithRatio; /** - * AccessListWrapper instance + * TransactionUtils instance */ - public AccessListWrapper accessList; + public TransactionUtils utils; /** * Creates a Transaction instance @@ -192,7 +193,7 @@ public TransactionWrapper(Klay klaytnCall) { this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); - this.accessList = new AccessListWrapper(); + this.utils = new TransactionUtils(new AccessListWrapper()); } /** diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index e296cc482..b999a8b06 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -19,8 +19,8 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TxPropertyBuilder; -import com.klaytn.caver.transaction.accessList.AccessList; -import com.klaytn.caver.transaction.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.wallet.keyring.AbstractKeyring; diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java index beb9615ef..35dbcdd04 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.klaytn.caver.Caver; -import com.klaytn.caver.transaction.accessList.AccessList; -import com.klaytn.caver.transaction.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.accessList.AccessTuple; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -61,7 +61,7 @@ public static class createInstance { @Test public void create() throws IOException { - AccessList accessList = caver.transaction.accessList.create( + AccessList accessList = caver.transaction.utils.accessList.create( Arrays.asList( new AccessTuple( "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", @@ -144,7 +144,7 @@ public void manyAccessList() throws IOException { public static class decodeTest { @Test public void decodeString() throws IOException { - AccessList decodedAccessList = caver.transaction.accessList.decode(encodedAccessList); + AccessList decodedAccessList = caver.transaction.utils.accessList.decode(encodedAccessList); System.out.println(objectToString(decodedAccessList)); Assert.assertTrue(accessList.equals(decodedAccessList)); } From 0d8655ab691a114afd90b6e05db6e16854b2aac8 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 17:39:13 +0900 Subject: [PATCH 16/89] add missed TransactionUtils class in previous commit --- .../transaction/utils/TransactionUtils.java | 37 +++++++++++++++++++ .../transaction/EthereumAccessListTest.java | 1 - 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java new file mode 100644 index 000000000..f2469fdd0 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.utils; + +import com.klaytn.caver.transaction.utils.accessList.wrapper.AccessListWrapper; + +/** + * TransactionUtils includes various helper classes for transaction packages. + */ +public class TransactionUtils { + /** + * AccessListWrapper instance. + */ + public AccessListWrapper accessList; + + /** + * Creates a TransactionUtils instance. + * @param accessList An instance of AccessListWrapper class. + */ + public TransactionUtils(AccessListWrapper accessList) { + this.accessList = accessList; + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index b999a8b06..546494d2f 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -451,7 +451,6 @@ public void getRLPEncodingAndDecoding() { // Test-4: decoding decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); - } @Test From dd504ebfd8d5870c1eaf6f3f23a38d19f767df48 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 17:43:50 +0900 Subject: [PATCH 17/89] Re-structure package structure related with access list --- .../com/klaytn/caver/methods/response/AccessListResult.java | 4 ++-- .../klaytn/caver/transaction/type/EthereumAccessList.java | 2 +- .../transaction/type/wrapper/EthereumAccessListWrapper.java | 2 +- .../transaction/utils/{accessList => }/AccessList.java | 2 +- .../transaction/utils/{accessList => }/AccessTuple.java | 2 +- .../klaytn/caver/transaction/utils/TransactionUtils.java | 2 +- .../utils/{accessList => }/wrapper/AccessListWrapper.java | 6 +++--- .../caver/transaction/wrapper/TransactionWrapper.java | 2 +- .../caver/common/transaction/EthereumAccessListTest.java | 4 ++-- .../caver/common/transaction/accessList/AccessListTest.java | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) rename core/src/main/java/com/klaytn/caver/transaction/utils/{accessList => }/AccessList.java (98%) rename core/src/main/java/com/klaytn/caver/transaction/utils/{accessList => }/AccessTuple.java (98%) rename core/src/main/java/com/klaytn/caver/transaction/utils/{accessList => }/wrapper/AccessListWrapper.java (91%) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java index effde2e10..feba51d98 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -22,8 +22,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.klaytn.caver.transaction.utils.accessList.AccessList; -import com.klaytn.caver.transaction.utils.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import org.web3j.protocol.core.Response; import java.io.IOException; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 017ce0677..9f8efae52 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -18,7 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; -import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java index 121a6ae72..73758d26c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -17,7 +17,7 @@ package com.klaytn.caver.transaction.type.wrapper; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.utils.accessList.AccessList; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.wallet.keyring.SignatureData; diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java similarity index 98% rename from core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java index 26dab31fa..1b972d02c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.utils.accessList; +package com.klaytn.caver.transaction.utils; import org.web3j.rlp.*; import org.web3j.utils.Numeric; diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java similarity index 98% rename from core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 56bbc3848..15eea6820 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.utils.accessList; +package com.klaytn.caver.transaction.utils; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java index f2469fdd0..a7235f924 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java @@ -16,7 +16,7 @@ package com.klaytn.caver.transaction.utils; -import com.klaytn.caver.transaction.utils.accessList.wrapper.AccessListWrapper; +import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; /** * TransactionUtils includes various helper classes for transaction packages. diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java similarity index 91% rename from core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java index f605a8330..9291ec1f6 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/accessList/wrapper/AccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.utils.accessList.wrapper; +package com.klaytn.caver.transaction.utils.wrapper; -import com.klaytn.caver.transaction.utils.accessList.AccessList; -import com.klaytn.caver.transaction.utils.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import org.web3j.rlp.RlpList; import java.util.List; diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 459f5ccc9..36c4fe226 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -21,7 +21,7 @@ import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.utils.TransactionUtils; -import com.klaytn.caver.transaction.utils.accessList.wrapper.AccessListWrapper; +import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; import com.klaytn.caver.transaction.type.wrapper.*; import java.util.List; diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 546494d2f..532dc9412 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -19,8 +19,8 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TxPropertyBuilder; -import com.klaytn.caver.transaction.utils.accessList.AccessList; -import com.klaytn.caver.transaction.utils.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.wallet.keyring.AbstractKeyring; diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java index 35dbcdd04..c650ac464 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.klaytn.caver.Caver; -import com.klaytn.caver.transaction.utils.accessList.AccessList; -import com.klaytn.caver.transaction.utils.accessList.AccessTuple; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; From bfc4643b350ac7be128e7923a0abc15e2e541338 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 20:11:10 +0900 Subject: [PATCH 18/89] Override appendSignatures and add test cases --- .../transaction/type/EthereumAccessList.java | 34 +- .../transaction/EthereumAccessListTest.java | 382 +++++++++++++++++- 2 files changed, 413 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 9f8efae52..fa04b530e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -160,7 +160,7 @@ public EthereumAccessList(EthereumAccessList.Builder builder) { public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { super( klaytnCall, - TransactionType.TxTypeLegacyTransaction.toString(), + TransactionType.TxTypeEthereumAccessList.toString(), from, nonce, gas, @@ -314,6 +314,38 @@ public static EthereumAccessList decode(byte[] rlpEncoded) { } } + /** + * Appends signatures array to transaction. + * EthereumAccessList transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData SignatureData instance contains ECDSA signature data + */ + @Override + public void appendSignatures(SignatureData signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures().get(0))) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + super.appendSignatures(signatureData); + } + + /** + * Appends signatures array to transaction. + * EthereumAccessList transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData List of SignatureData contains ECDSA signature data + */ + @Override + public void appendSignatures(List signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + if(signatureData.size() != 1) { + throw new RuntimeException("Signatures are too long " + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + super.appendSignatures(signatureData); + } + /** * Getter function for to * diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 532dc9412..9b49d3886 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -19,10 +19,10 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TxPropertyBuilder; -import com.klaytn.caver.transaction.utils.AccessList; -import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.wallet.keyring.AbstractKeyring; import com.klaytn.caver.wallet.keyring.SignatureData; import org.junit.Rule; @@ -564,4 +564,382 @@ public void throwException_NoChainId() { } + public static class combineSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); +; + @Test + public void combineSignature() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String chainID = "0x1"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x0000000000000000000000000000000000000002", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4"), + Numeric.hexStringToByteArray("0x782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb") + ); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fcf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumAccessList.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + } + + @Test + public void combineSignature_EmptySig() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + + SignatureData emptySig = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + .setSignatures(emptySig) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumAccessList.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + } + + @Test + public void throwException_existSignature() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x0fea"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumAccessList.combineSignedRawTransactions(rlpList); + } + + @Test + public void throwException_differentField() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Transactions containing different information cannot be combined."); + + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + // rlpEncoded is a rlp encoded string of EthereumAccessList transaction containing above fields. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + // All fields are same with above rlpEncoded EthereumAccessList transaction except accessList field. + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + ))) + .setTo(to) + ); + + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumAccessList.combineSignedRawTransactions(rlpList); + } + } + + public static class appendSignaturesTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String to = "0x8723590d5D60e35f7cE0Db5C09D3938b26fF80Ae"; + static BigInteger value = BigInteger.ONE; + static BigInteger gas = BigInteger.valueOf(90000); + static String gasPrice = "0x19"; + static String nonce = "0x3a"; + static BigInteger chainID = BigInteger.valueOf(2019); + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0fea"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + ); + + @Test + public void appendSignature() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + ); + + ethereumAccessList.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void appendSignatureWithEmptySig() { + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumAccessList.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void appendSignatureList() { + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + .setTo(to) + ); + + ethereumAccessList.appendSignatures(list); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void appendSignatureList_EmptySig() { + List list = new ArrayList<>(); + list.add(signatureData); + + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumAccessList.appendSignatures(list); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void throwException_appendData_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.appendSignatures(signatureData); + } + + @Test + public void throwException_appendList_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(list) + ); + + ethereumAccessList.appendSignatures(list); + } + + @Test + public void throwException_tooLongSignatures() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures are too long " + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setAccessList(accessList) + .setValue(value) + ); + + ethereumAccessList.appendSignatures(list); + } + } + } From 147aba1d0f2f1be45ac9d2ed644dcdb41b8c1178 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 20:45:20 +0900 Subject: [PATCH 19/89] Override getTransactionHash and add test case --- .../transaction/type/EthereumAccessList.java | 12 +- .../transaction/EthereumAccessListTest.java | 119 +++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index fa04b530e..0d81fdf70 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -22,6 +22,7 @@ import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; +import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; @@ -176,7 +177,7 @@ public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas @Override public String getRLPEncoding() { - // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + // TransactionPayload = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) this.validateOptionalValues(true); List rlpTypeList = new ArrayList<>(); @@ -346,6 +347,15 @@ public void appendSignatures(List signatureData) { super.appendSignatures(signatureData); } + @Override + public String getTransactionHash() { + // TxHashRLP = 0x01 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + String rlpEncoded = this.getRLPEncoding(); + byte[] rlpEncodedBytes = Numeric.hexStringToByteArray(rlpEncoded); + byte[] detachedType = Arrays.copyOfRange(rlpEncodedBytes, 1, rlpEncodedBytes.length); + return Hash.sha3(Numeric.toHexString(detachedType)); + } + /** * Getter function for to * diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 9b49d3886..ba4b5dcf5 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -381,7 +381,7 @@ public void getRLPEncodingAndDecoding() { ); // Test-1: encoding EthereumAccessList which have an address and a storage key. - String expectedRLP ="0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; + String expectedRLP = "0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( TxPropertyBuilder.ethereumAccessList() .setFrom(null) @@ -564,6 +564,123 @@ public void throwException_NoChainId() { } + public static class getTransactionHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x26e8a6755fbf9d8d32271753c332718825881fef855e7f9e4868e2d16f908caa"), + Numeric.hexStringToByteArray("0x412c0e1dc1fe87fcc7b262be995701dc0606038844f8a0da1eaddef965d8fc2b") + ); + + @Test + public void getTransactionHash() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + String expected = "0x327372b5cd4f8d8fdbd12ea236da247b87ccfd8ed2e2c3b6ad65965f872474e7"; + String txHash = ethereumAccessList.getTransactionHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.getTransactionHash(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getRawTransaction(); + } + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getTransactionHash(); + } + } + + public static class combineSignatureTest { @Rule public ExpectedException expectedException = ExpectedException.none(); From 0b8910fe453ebc8eacf4d0d2773ae5a480399145 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 21:23:01 +0900 Subject: [PATCH 20/89] Add getRLPEncodingForSignatureTest for EthereumAccessList --- .../transaction/EthereumAccessListTest.java | 108 +++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index ba4b5dcf5..dd2155f06 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -680,7 +680,6 @@ public void throwException_NotDefined_ChainId() { } } - public static class combineSignatureTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -1059,4 +1058,111 @@ public void throwException_tooLongSignatures() { } } + public static class getRLPEncodingForSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + @Test + public void getRLPEncodingForSignature() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + + String expected = "0x01f9011401040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7cf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + String rlpEncodedSign = ethereumAccessList.getRLPEncodingForSignature(); + + assertEquals(expected, rlpEncodedSign); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + + } + + @Test + public void throwException_NotDefined_ChainID() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + } + } + } From d5e98d9e4bd1c550051db7ef00903231ff9016fb Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 21:25:52 +0900 Subject: [PATCH 21/89] Add getSenderTxHashTest for EthereumAccessList --- .../transaction/EthereumAccessListTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index dd2155f06..25454f00f 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -680,6 +680,122 @@ public void throwException_NotDefined_ChainId() { } } + public static class getSenderTxHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x26e8a6755fbf9d8d32271753c332718825881fef855e7f9e4868e2d16f908caa"), + Numeric.hexStringToByteArray("0x412c0e1dc1fe87fcc7b262be995701dc0606038844f8a0da1eaddef965d8fc2b") + ); + + @Test + public void getTransactionHash() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + String expected = "0x327372b5cd4f8d8fdbd12ea236da247b87ccfd8ed2e2c3b6ad65965f872474e7"; + String txHash = ethereumAccessList.getSenderTxHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getSenderTxHash(); + } + } + public static class combineSignatureTest { @Rule public ExpectedException expectedException = ExpectedException.none(); From 48d492a64bfbb25b7b93f524dd9d6ebb46352d45 Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 21:32:17 +0900 Subject: [PATCH 22/89] Change to assertEquals This is for checking whether AccessTuple.equals is working or not. --- .../caver/common/transaction/accessList/AccessListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java index c650ac464..37faf2672 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java @@ -146,7 +146,7 @@ public static class decodeTest { public void decodeString() throws IOException { AccessList decodedAccessList = caver.transaction.utils.accessList.decode(encodedAccessList); System.out.println(objectToString(decodedAccessList)); - Assert.assertTrue(accessList.equals(decodedAccessList)); + Assert.assertEquals(accessList, decodedAccessList); } } From 71a80311badd10706aaa1d887d9c51185dded7bc Mon Sep 17 00:00:00 2001 From: Denver Date: Wed, 2 Mar 2022 21:50:19 +0900 Subject: [PATCH 23/89] Add AccessList and TransactionUtils class AccessList * By EIP-2930 AccessList is a list of AccessTuple. * It contains some methods to help encoding and decoding access list. --- .../methods/response/AccessListResult.java | 15 +- .../caver/transaction/utils/AccessList.java | 111 ++++++++++++ .../{type => utils}/AccessTuple.java | 46 ++++- .../transaction/utils/TransactionUtils.java | 37 ++++ .../utils/wrapper/AccessListWrapper.java | 77 +++++++++ .../wrapper/TransactionWrapper.java | 9 + .../com/klaytn/caver/common/rpc/RpcTest.java | 8 +- .../accessList/AccessListTest.java | 163 ++++++++++++++++++ 8 files changed, 448 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java rename core/src/main/java/com/klaytn/caver/transaction/{type => utils}/AccessTuple.java (83%) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java create mode 100644 core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java index 5d9cf6ab2..feba51d98 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -22,12 +22,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.klaytn.caver.transaction.type.AccessTuple; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import org.web3j.protocol.core.Response; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; public class AccessListResult extends Response { @@ -38,21 +37,21 @@ public void setResult(AccessListResultData result) { @JsonDeserialize(using = AccessListResultData.AccessListResultDeserializer.class) public static class AccessListResultData { - List accessList; + AccessList accessList; String error; String gasUsed; - public AccessListResultData(List accessList, String error, String gasUsed) { + public AccessListResultData(AccessList accessList, String error, String gasUsed) { this.accessList = accessList; this.error = error; this.gasUsed = gasUsed; } - public List getAccessList() { + public AccessList getAccessList() { return accessList; } - public void setAccessList(List accessList) { + public void setAccessList(AccessList accessList) { this.accessList = accessList; } @@ -78,7 +77,7 @@ public AccessListResultData deserialize(JsonParser p, DeserializationContext ctx ObjectMapper objectMapper = new ObjectMapper(); JsonNode node = p.getCodec().readTree(p); - List accessList = new ArrayList<>(); + AccessList accessList = new AccessList(); JsonNode accessListNode = node.get("accessList"); for (JsonNode innerNode : accessListNode) { AccessTuple accessTuple = objectMapper.treeToValue(innerNode, AccessTuple.class); diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java new file mode 100644 index 000000000..1b972d02c --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.utils; + +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.util.*; + +/** + * Represents an access list. + * AccessList is an EIP-2930 access list. + */ +public class AccessList extends ArrayList { + /** + * Constructor. + */ + public AccessList() {} + + /** + * Creates an AccessList instance from given list of AccessTuple. + * @param items A list of AccessTuple. + */ + public AccessList(List items) { + this.addAll(items); + } + + /** + * Creates an Access list. + * + * @param items + * @return + */ + public static AccessList create(List items) { + AccessList accessList = new AccessList(items); + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param accessListRlpList List of RlpType to decode. + * @return AccessList + */ + public static AccessList decode(RlpList accessListRlpList) { + List accessTupleRlps = accessListRlpList.getValues(); + AccessList accessList = new AccessList(); + for (int i =0 ; i < accessTupleRlps.size(); i++) { + accessList.add(AccessTuple.decode((RlpList) accessTupleRlps.get(i))); + } + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded string of access list. + * @return + */ + public static AccessList decode(String rlpEncoded) { + return AccessList.decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded byte array of access list. + * @return + */ + public static AccessList decode(byte[] rlpEncoded) { + RlpList rlpList = RlpDecoder.decode(rlpEncoded); + return AccessList.decode(((RlpList) rlpList.getValues().get(0))); + } + + /** + * Returns the RLP-encoded string of this accessList. + * + * @return RlpList + */ + public RlpList toRlpList() { + List rlpTypeList = new ArrayList<>(); + for (AccessTuple accessTuple : this) { + rlpTypeList.add(accessTuple.toRlpList()); + } + return new RlpList(rlpTypeList); + } + + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java similarity index 83% rename from core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java rename to core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 6723308a0..143a4905d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.klaytn.caver.transaction.type; +package com.klaytn.caver.transaction.utils; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.web3j.rlp.RlpEncoder; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; import org.web3j.rlp.RlpType; @@ -39,39 +40,61 @@ public class AccessTuple { private String address; private List storageKeys; + /** + * Create an AccessTuple instance. + * @param address An address string. + * @param storageKeys A list of storage keys. + */ public AccessTuple(String address, List storageKeys) { this.address = address; this.storageKeys = storageKeys; } + /** + * Getter function of address. + * @return String + */ public String getAddress() { return address; } + /** + * Setter function of address. + * @param address + */ public void setAddress(String address) { this.address = address; } + /** + * Getter function of storageKeys. + * @return List<String> + */ public List getStorageKeys() { return storageKeys; } + /** + * Setter function of storageKeys. + * @param storageKeys A list of storage keys. + */ public void setStorageKeys(List storageKeys) { this.storageKeys = storageKeys; } + /** + * Decodes given RlpList to AccessTuple. + * @param rlpEncodedAccessTuple + * @return + */ public static AccessTuple decode(RlpList rlpEncodedAccessTuple) { try { List accessTupleRlp = rlpEncodedAccessTuple.getValues(); - if (accessTupleRlp.size() <= 0) { - return null; - } String address = ((RlpString) accessTupleRlp.get(0)).asString(); List storageKeys = new ArrayList<>(); List storageKeysRlpType = ((RlpList) (accessTupleRlp.get(1))).getValues(); for (RlpType storageKeyRlpType : storageKeysRlpType) { - storageKeys.add(((RlpString) storageKeyRlpType).asString()); - } + storageKeys.add(((RlpString) storageKeyRlpType).asString()); } return new AccessTuple(address, storageKeys); } catch (Exception e) { throw new RuntimeException("There is an error while decoding process."); @@ -95,6 +118,17 @@ public RlpList toRlpList() { ); } + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } + + /** * Indicates whether some other object is "equal to" this one. * diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java new file mode 100644 index 000000000..a7235f924 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.utils; + +import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; + +/** + * TransactionUtils includes various helper classes for transaction packages. + */ +public class TransactionUtils { + /** + * AccessListWrapper instance. + */ + public AccessListWrapper accessList; + + /** + * Creates a TransactionUtils instance. + * @param accessList An instance of AccessListWrapper class. + */ + public TransactionUtils(AccessListWrapper accessList) { + this.accessList = accessList; + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java new file mode 100644 index 000000000..9291ec1f6 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.utils.wrapper; + +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.web3j.rlp.RlpList; + +import java.util.List; + +/** + * Represents a AccessListWrapper + * 1. This class wraps all of static methods of AccessList + * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + */ +public class AccessListWrapper { + /** + * Creates a AccessListWrapper instance. + */ + public AccessListWrapper() { + } + + /** + * Creates a AccessList instance derived from a RLP-encoded AccessList string. + * + * @param items A list of AccessTuple + * @return AccessList + */ + public AccessList create(List items) { + return AccessList.create(items); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(String rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(byte[] rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + + /** + * Decodes a RLP-encoded AccessList. + * + * @param rlpTypeList List of RlpType to decode. + * @return AccessList + */ + public AccessList decode(RlpList rlpTypeList) { + return AccessList.decode(rlpTypeList); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 52e73b1d3..c1e4ebdf9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -21,6 +21,8 @@ import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.type.wrapper.*; +import com.klaytn.caver.transaction.utils.TransactionUtils; +import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; import java.util.List; @@ -143,6 +145,11 @@ public class TransactionWrapper { */ public FeeDelegatedChainDataAnchoringWithRatioWrapper feeDelegatedChainDataAnchoringWithRatio; + /** + * TransactionUtils instance + */ + public TransactionUtils utils; + /** * Creates a Transaction instance * @param klaytnCall Klay RPC instance @@ -179,6 +186,8 @@ public TransactionWrapper(Klay klaytnCall) { this.chainDataAnchoring = new ChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); + + this.utils = new TransactionUtils(new AccessListWrapper()); } /** diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index f4dc21278..5f9921eec 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -464,8 +464,8 @@ public static class otherRPCTest { static TransactionReceipt.TransactionReceiptData sampleReceiptData; private static TransactionReceipt.TransactionReceiptData sendKlay() throws IOException, TransactionException { - Caver caver = new Caver(Caver.DEFAULT_URL); - AbstractKeyring keyring = caver.wallet.add(KeyringFactory.createFromPrivateKey("0x871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5")); + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.add(KeyringFactory.createFromPrivateKey("0x871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5")); BigInteger value = new BigInteger(Utils.convertToPeb(BigDecimal.ONE, "KLAY")); @@ -945,8 +945,8 @@ public void getFeeHistoryTest() throws IOException, InterruptedException { nonce = nonce.add(BigInteger.valueOf(1)); if (i != txsCount - 1) { - caver.rpc.klay.sendRawTransaction(tx).send(); - continue; + caver.rpc.klay.sendRawTransaction(tx).send(); + continue; } Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(tx).send(); Thread.sleep(5000); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java new file mode 100644 index 000000000..37faf2672 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction.accessList; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessListTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + static Caver caver = new Caver(); + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessList accessList = caver.transaction.utils.accessList.create( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ) + ) + ); + } + } + + public static class deserializeTest { + + @Test + public void oneAddressAndOneStorageKey() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void oneAddressAndManyStorageKeys() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void manyAccessList() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + } + + public static class decodeTest { + @Test + public void decodeString() throws IOException { + AccessList decodedAccessList = caver.transaction.utils.accessList.decode(encodedAccessList); + System.out.println(objectToString(decodedAccessList)); + Assert.assertEquals(accessList, decodedAccessList); + } + } + + public static class encodeTest { + @Test + public void encodeToBytes() { + byte[] encoded = accessList.encodeToBytes(); + String encodedString = Numeric.toHexString(encoded); + + AccessList.decode(encodedString); + Assert.assertEquals(encodedAccessList, encodedString); + } + } +} From 508b3323cb1dcb8dbd49cf943d5bf432e988a995 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 00:19:41 +0900 Subject: [PATCH 24/89] Add helper method to check isEthereumTransaction orn ot --- .../transaction/AbstractTransaction.java | 20 +++++------ .../transaction/type/TransactionType.java | 33 ++++++++++++++++++- .../transaction/LegacyTransactionTest.java | 10 +++--- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index ba7658e96..c4342ede9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -21,8 +21,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.AccountKeyRoleBased; -import com.klaytn.caver.transaction.type.EthereumAccessList; -import com.klaytn.caver.transaction.type.LegacyTransaction; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.AbstractKeyring; @@ -269,8 +267,8 @@ public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { - if(this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()) && keyring.isDecoupled()) { - throw new IllegalArgumentException("A legacy transaction cannot be signed with a decoupled keyring."); + if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } if(this.from.equals("0x") || this.from.equals(Utils.DEFAULT_ZERO_ADDRESS)){ @@ -313,8 +311,8 @@ public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOExc * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { - if(this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()) && keyring.isDecoupled()) { - throw new IllegalArgumentException("A legacy transaction cannot be signed with a decoupled keyring."); + if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } if(this.from.equals("0x") || this.from.equals(Utils.DEFAULT_ZERO_ADDRESS)){ @@ -515,12 +513,12 @@ public void validateOptionalValues(boolean checkChainID) { * @return List<String> */ public List refineSignature(List signatureDataList) { - boolean isLegacy = this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()); + boolean isEthereumTransaction = TransactionType.isEthereumTransaction(this.getType()); List refinedList = SignatureData.refineSignature(signatureDataList); - if(isLegacy && refinedList.size() > 1) { - throw new RuntimeException("LegacyTransaction cannot have multiple signature."); + if(isEthereumTransaction && refinedList.size() > 1) { + throw new RuntimeException(this.getType() + " cannot have multiple signature."); } return refinedList; @@ -648,8 +646,8 @@ public void setType(String type) { } public void setFrom(String from) { - //"From" field in LegacyTransaction or EthereumAccessList allows null - if(this instanceof LegacyTransaction || this instanceof EthereumAccessList) { + //"From" field in EthereumTransaction allows null + if(TransactionType.isEthereumTransaction(this.getType())) { if(from == null || from.isEmpty() || from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS)) from = Utils.DEFAULT_ZERO_ADDRESS; } else { if(from == null) { diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 09a5ffaac..17657c4eb 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -16,6 +16,8 @@ package com.klaytn.caver.transaction.type; +import java.util.Objects; + public enum TransactionType { TxTypeLegacyTransaction(0x00), @@ -50,7 +52,7 @@ public enum TransactionType { TxTypeEthereumAccessList(0x7801); int type; - + TransactionType(int type) { this.type = type; } @@ -58,4 +60,33 @@ public enum TransactionType { public int getType() { return type; } + + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type integer. + * @return + */ + public static boolean isEthereumTransaction(int type) { + if (type == TxTypeLegacyTransaction.type || type == TxTypeEthereumAccessList.type) { + return true; + } else return false; + } + + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type string. + * @return + */ + public static boolean isEthereumTransaction(String type) { + if ( + Objects.equals(type, TxTypeLegacyTransaction.toString()) || + Objects.equals(type, TxTypeEthereumAccessList.toString()) + ) { + return true; + } else return false; + } + + } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java index 2d4ecf1e7..8ec12074a 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java @@ -369,7 +369,7 @@ public void signWithKey_KeyString_NoIndex() throws IOException { @Test public void throwException_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); legacyTransaction.sign(deCoupledKeyring); @@ -483,7 +483,7 @@ public void signWithKeys_KeyString_NoSigner() throws IOException { @Test public void throwException_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); legacyTransaction.sign(deCoupledKeyring); @@ -492,7 +492,7 @@ public void throwException_decoupledKey() throws IOException { @Test public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); @@ -503,7 +503,7 @@ public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOExcepti @Test public void throwException_multipleKeyring() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransactionn cannot be signed with a decoupled keyring."); String[] privateKeyArr = { caver.wallet.keyring.generateSingleKey(), @@ -522,7 +522,7 @@ public void throwException_multipleKeyring() throws IOException { @Test public void throwException_roleBasedKeyring() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); String[][] privateKeyArr = { { From 7ad69d999ce42794b51f657af5a8ef9e9bc9b624 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 01:11:11 +0900 Subject: [PATCH 25/89] Fix typo --- .../klaytn/caver/common/transaction/LegacyTransactionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java index 8ec12074a..ca67cf18c 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java @@ -503,7 +503,7 @@ public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOExcepti @Test public void throwException_multipleKeyring() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("TxTypeLegacyTransactionn cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); String[] privateKeyArr = { caver.wallet.keyring.generateSingleKey(), From 210b562e9a69bcffb43f7f6226986142ff0e0b74 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 01:33:48 +0900 Subject: [PATCH 26/89] Ignore cases when comparing accessTuple --- .../com/klaytn/caver/transaction/utils/AccessTuple.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 15eea6820..fdec142fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -146,12 +146,17 @@ public boolean equals(Object o) { AccessTuple that = (AccessTuple) o; - if (!address.equals(that.address)) { + if (!address.equalsIgnoreCase(that.address)) { return false; } - if (!storageKeys.equals(that.storageKeys)) { + if (storageKeys.size() != that.storageKeys.size()) { return false; } + for (int i = 0; i < storageKeys.size(); i++) { + if (!storageKeys.get(i).equalsIgnoreCase(that.storageKeys.get(i))) { + return false; + } + } return true; } From e6290b18d35f9001921f8c195bd1029297e6e546 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 01:48:42 +0900 Subject: [PATCH 27/89] Check values when setting fields of AccessTuple. --- .../klaytn/caver/transaction/utils/AccessTuple.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index fdec142fa..4a948b559 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.klaytn.caver.utils.Utils; import org.web3j.rlp.RlpEncoder; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; @@ -63,7 +64,10 @@ public String getAddress() { * @param address */ public void setAddress(String address) { - this.address = address; + if (!Utils.isAddress(address)) { + throw new IllegalArgumentException("Invalid address. Address: " + address); + } + this.address = Utils.addHexPrefix(address); } /** @@ -79,6 +83,11 @@ public List getStorageKeys() { * @param storageKeys A list of storage keys. */ public void setStorageKeys(List storageKeys) { + for (String storageKey : storageKeys) { + if (!Utils.isHex(storageKey)) { + throw new IllegalArgumentException("Invalid storageKey. Storage key should be a hex string " + storageKey); + } + } this.storageKeys = storageKeys; } From 8bf3220db7539ab33d67a70fa93c9ae56aa58018 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 02:26:13 +0900 Subject: [PATCH 28/89] Update AccessTuple and add tests --- .../caver/transaction/utils/AccessTuple.java | 14 ++- .../{accessList => utils}/AccessListTest.java | 4 +- .../transaction/utils/AccessTupleTest.java | 100 ++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) rename core/src/test/java/com/klaytn/caver/common/transaction/{accessList => utils}/AccessListTest.java (98%) create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 4a948b559..54e5f98a3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -47,8 +47,8 @@ public class AccessTuple { * @param storageKeys A list of storage keys. */ public AccessTuple(String address, List storageKeys) { - this.address = address; - this.storageKeys = storageKeys; + this.setAddress(address); + this.setStorageKeys(storageKeys); } /** @@ -83,10 +83,14 @@ public List getStorageKeys() { * @param storageKeys A list of storage keys. */ public void setStorageKeys(List storageKeys) { - for (String storageKey : storageKeys) { - if (!Utils.isHex(storageKey)) { - throw new IllegalArgumentException("Invalid storageKey. Storage key should be a hex string " + storageKey); + for (int i = 0; i < storageKeys.size(); i++) { + // This is for handling when given storageKey has large hex prefix "0X". + // Hex prefix must be used as small hex prefix "0x". + String storageKey = Utils.addHexPrefix(Utils.stripHexPrefix(storageKeys.get(i))); + if (!Utils.isHex(storageKey) || storageKey.length() != 66) { + throw new IllegalArgumentException("Invalid storageKey. Storage key should be a 32 bytes of hex string " + storageKey); } + storageKeys.set(i, storageKey); } this.storageKeys = storageKeys; } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java similarity index 98% rename from core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java rename to core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java index 37faf2672..042137612 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.common.transaction.accessList; +package com.klaytn.caver.common.transaction.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -55,7 +55,7 @@ public static String objectToString(Object value) throws JsonProcessingException )); static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; - public static class createInstance { + public static class createInstanceTest { @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java new file mode 100644 index 000000000..a73bd69a4 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessTupleTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class createInstanceTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessTuple expectedAccessTuple = new AccessTuple( + "0x4173c51bd0e64a4c58656a5401373a476e8534ec", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ); + + AccessTuple accessTuple = new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + + // Checksum address and upper case letter should be accepted. + Assert.assertEquals(expectedAccessTuple, accessTuple); + Assert.assertTrue(Arrays.equals(expectedAccessTuple.encodeToBytes(), accessTuple.encodeToBytes())); + } + + @Test + public void create_invalidAddress() { + // 1 byte-short + String invalidAddress = "0x4173c51bd0e64a4c58656a5401373a476e8534"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid address. Address: " + invalidAddress); + + new AccessTuple( + invalidAddress, + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + + @Test + public void create_invalidStorageKey() { + // 1 byte-short + String invalidStorageKey = "0x4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey); + + new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + invalidStorageKey, + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + } +} From 744845fa531bb86a662b97c41dc794325bb8671e Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 02:29:28 +0900 Subject: [PATCH 29/89] Update AccessTuple and added test for it too --- .../caver/transaction/utils/AccessTuple.java | 28 ++++- .../{accessList => utils}/AccessListTest.java | 4 +- .../transaction/utils/AccessTupleTest.java | 100 ++++++++++++++++++ 3 files changed, 125 insertions(+), 7 deletions(-) rename core/src/test/java/com/klaytn/caver/common/transaction/{accessList => utils}/AccessListTest.java (98%) create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 143a4905d..cc5b22c9d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.klaytn.caver.utils.Utils; import org.web3j.rlp.RlpEncoder; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; @@ -46,8 +47,8 @@ public class AccessTuple { * @param storageKeys A list of storage keys. */ public AccessTuple(String address, List storageKeys) { - this.address = address; - this.storageKeys = storageKeys; + this.setAddress(address); + this.setStorageKeys(storageKeys); } /** @@ -63,7 +64,10 @@ public String getAddress() { * @param address */ public void setAddress(String address) { - this.address = address; + if (!Utils.isAddress(address)) { + throw new IllegalArgumentException("Invalid address. Address: " + address); + } + this.address = Utils.addHexPrefix(address); } /** @@ -79,6 +83,15 @@ public List getStorageKeys() { * @param storageKeys A list of storage keys. */ public void setStorageKeys(List storageKeys) { + for (int i = 0; i < storageKeys.size(); i++) { + // This is for handling when given storageKey has large hex prefix "0X". + // Hex prefix must be used as small hex prefix "0x". + String storageKey = Utils.addHexPrefix(Utils.stripHexPrefix(storageKeys.get(i))); + if (!Utils.isHex(storageKey) || storageKey.length() != 66) { + throw new IllegalArgumentException("Invalid storageKey. Storage key should be a 32 bytes of hex string " + storageKey); + } + storageKeys.set(i, storageKey); + } this.storageKeys = storageKeys; } @@ -146,12 +159,17 @@ public boolean equals(Object o) { AccessTuple that = (AccessTuple) o; - if (!address.equals(that.address)) { + if (!address.equalsIgnoreCase(that.address)) { return false; } - if (!storageKeys.equals(that.storageKeys)) { + if (storageKeys.size() != that.storageKeys.size()) { return false; } + for (int i = 0; i < storageKeys.size(); i++) { + if (!storageKeys.get(i).equalsIgnoreCase(that.storageKeys.get(i))) { + return false; + } + } return true; } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java similarity index 98% rename from core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java rename to core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java index 37faf2672..042137612 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/accessList/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.klaytn.caver.common.transaction.accessList; +package com.klaytn.caver.common.transaction.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -55,7 +55,7 @@ public static String objectToString(Object value) throws JsonProcessingException )); static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; - public static class createInstance { + public static class createInstanceTest { @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java new file mode 100644 index 000000000..a73bd69a4 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessTupleTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class createInstanceTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessTuple expectedAccessTuple = new AccessTuple( + "0x4173c51bd0e64a4c58656a5401373a476e8534ec", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ); + + AccessTuple accessTuple = new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + + // Checksum address and upper case letter should be accepted. + Assert.assertEquals(expectedAccessTuple, accessTuple); + Assert.assertTrue(Arrays.equals(expectedAccessTuple.encodeToBytes(), accessTuple.encodeToBytes())); + } + + @Test + public void create_invalidAddress() { + // 1 byte-short + String invalidAddress = "0x4173c51bd0e64a4c58656a5401373a476e8534"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid address. Address: " + invalidAddress); + + new AccessTuple( + invalidAddress, + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + + @Test + public void create_invalidStorageKey() { + // 1 byte-short + String invalidStorageKey = "0x4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey); + + new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + invalidStorageKey, + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + } +} From bf421de39086676594d82e459da5a6070f4a4c03 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 09:20:52 +0900 Subject: [PATCH 30/89] Convert small case when set storage keys and order that keys. --- .../caver/transaction/utils/AccessTuple.java | 5 ++-- .../transaction/utils/AccessTupleTest.java | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index cc5b22c9d..7f50e4dff 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -84,14 +84,13 @@ public List getStorageKeys() { */ public void setStorageKeys(List storageKeys) { for (int i = 0; i < storageKeys.size(); i++) { - // This is for handling when given storageKey has large hex prefix "0X". - // Hex prefix must be used as small hex prefix "0x". - String storageKey = Utils.addHexPrefix(Utils.stripHexPrefix(storageKeys.get(i))); + String storageKey = Utils.addHexPrefix(storageKeys.get(i).toLowerCase()); if (!Utils.isHex(storageKey) || storageKey.length() != 66) { throw new IllegalArgumentException("Invalid storageKey. Storage key should be a 32 bytes of hex string " + storageKey); } storageKeys.set(i, storageKey); } + java.util.Collections.sort(storageKeys); this.storageKeys = storageKeys; } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java index a73bd69a4..414d33bfe 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -60,7 +60,8 @@ public void create() throws IOException { ) ); - // Checksum address and upper case letter should be accepted. + // Checksum address should be accepted. + // Uppercase storage keys will be converted as small case letter when creating AccessTuple. Assert.assertEquals(expectedAccessTuple, accessTuple); Assert.assertTrue(Arrays.equals(expectedAccessTuple.encodeToBytes(), accessTuple.encodeToBytes())); } @@ -86,7 +87,7 @@ public void create_invalidStorageKey() { // 1 byte-short String invalidStorageKey = "0x4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B"; expectedException.expect(RuntimeException.class); - expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey); + expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey.toLowerCase()); new AccessTuple( "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", @@ -96,5 +97,28 @@ public void create_invalidStorageKey() { ) ); } + + @Test + public void create_orderedKeys() { + AccessTuple accessTuple = new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717", + "0XaF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XbF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XcF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98" + ) + ); + Assert.assertEquals( + Arrays.asList( + "0xaf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xbf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717", + "0xcf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98" + ), + accessTuple.getStorageKeys() + ); + } + } } From 141bc08de7b141441df751e22ad8bcdf62b6e509 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 09:21:41 +0900 Subject: [PATCH 31/89] Convert small case when set storage keys and order that keys. --- .../caver/transaction/utils/AccessTuple.java | 7 ++--- .../transaction/utils/AccessTupleTest.java | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 54e5f98a3..7f50e4dff 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -84,14 +84,13 @@ public List getStorageKeys() { */ public void setStorageKeys(List storageKeys) { for (int i = 0; i < storageKeys.size(); i++) { - // This is for handling when given storageKey has large hex prefix "0X". - // Hex prefix must be used as small hex prefix "0x". - String storageKey = Utils.addHexPrefix(Utils.stripHexPrefix(storageKeys.get(i))); + String storageKey = Utils.addHexPrefix(storageKeys.get(i).toLowerCase()); if (!Utils.isHex(storageKey) || storageKey.length() != 66) { throw new IllegalArgumentException("Invalid storageKey. Storage key should be a 32 bytes of hex string " + storageKey); } storageKeys.set(i, storageKey); } + java.util.Collections.sort(storageKeys); this.storageKeys = storageKeys; } @@ -133,7 +132,7 @@ public RlpList toRlpList() { /** * Returns an encoded access tuple. - * + * * @return byte[] */ public byte[] encodeToBytes() { diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java index a73bd69a4..b24bd650b 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -60,7 +60,8 @@ public void create() throws IOException { ) ); - // Checksum address and upper case letter should be accepted. + // Checksum address should be accepted. + // Uppercase storage keys will be converted as small case letter when creating AccessTuple. Assert.assertEquals(expectedAccessTuple, accessTuple); Assert.assertTrue(Arrays.equals(expectedAccessTuple.encodeToBytes(), accessTuple.encodeToBytes())); } @@ -86,7 +87,7 @@ public void create_invalidStorageKey() { // 1 byte-short String invalidStorageKey = "0x4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B"; expectedException.expect(RuntimeException.class); - expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey); + expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey.toLowerCase()); new AccessTuple( "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", @@ -96,5 +97,28 @@ public void create_invalidStorageKey() { ) ); } + + @Test + public void create_orderedKeys() { + AccessTuple accessTuple = new AccessTuple( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717", + "0XaF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XbF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XcF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98" + ) + ); + Assert.assertEquals( + Arrays.asList( + "0xaf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xbf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717", + "0xcf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98" + ), + accessTuple.getStorageKeys() + ); + } + } } From 4bb61a21d0a91889ae88deee0c4c2d368f3bc6f3 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 09:56:40 +0900 Subject: [PATCH 32/89] Update test case Now storage keys are ordered so expected rlp encoded value should be updated too. --- .../klaytn/caver/common/transaction/utils/AccessListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java index 042137612..c07593f0f 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java @@ -53,7 +53,7 @@ public static String objectToString(Object value) throws JsonProcessingException "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" )) )); - static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681"; public static class createInstanceTest { @Rule From 64be93ab32ec1887412cf6a228e31f8acadde3e2 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 10:14:50 +0900 Subject: [PATCH 33/89] Updated test cases Now storage keys are stored as ordered. --- .../transaction/EthereumAccessListTest.java | 16 +++++++--------- .../common/transaction/utils/AccessListTest.java | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 25454f00f..a5ee2a27a 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -399,7 +399,7 @@ public void getRLPEncodingAndDecoding() { assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-2: encoding EthereumAccessList which have many storageKeys - expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; ethereumAccessList = caver.transaction.ethereumAccessList.create( TxPropertyBuilder.ethereumAccessList() .setFrom(null) @@ -436,7 +436,7 @@ public void getRLPEncodingAndDecoding() { assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-4: encoding EthereumAccessList which have many accessList - expectedRLP = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fcf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + expectedRLP = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; ethereumAccessList = caver.transaction.ethereumAccessList.create( TxPropertyBuilder.ethereumAccessList() .setFrom(null) @@ -614,8 +614,7 @@ public void getTransactionHash() { .setAccessList(accessList) .setSignatures(signatureData) ); - - String expected = "0x327372b5cd4f8d8fdbd12ea236da247b87ccfd8ed2e2c3b6ad65965f872474e7"; + String expected = "0x5f4484fe404d9e5b3c100d1b67aa5633daab5d516267bf8af03550644e2d7378"; String txHash = ethereumAccessList.getTransactionHash(); assertEquals(expected, txHash); @@ -730,8 +729,7 @@ public void getTransactionHash() { .setAccessList(accessList) .setSignatures(signatureData) ); - - String expected = "0x327372b5cd4f8d8fdbd12ea236da247b87ccfd8ed2e2c3b6ad65965f872474e7"; + String expected = "0x5f4484fe404d9e5b3c100d1b67aa5633daab5d516267bf8af03550644e2d7378"; String txHash = ethereumAccessList.getSenderTxHash(); assertEquals(expected, txHash); @@ -841,7 +839,7 @@ public void combineSignature() { ); // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. - String rlpEncoded = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fcf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + String rlpEncoded = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; List rlpList = new ArrayList<>(); rlpList.add(rlpEncoded); String combined = ethereumAccessList.combineSignedRawTransactions(rlpList); @@ -887,7 +885,7 @@ public void combineSignature_EmptySig() { ); // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. - String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; List rlpList = new ArrayList<>(); rlpList.add(rlpEncoded); @@ -939,7 +937,7 @@ public void throwException_existSignature() { ); // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. - String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; List rlpList = new ArrayList<>(); rlpList.add(rlpEncoded); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java index 042137612..c07593f0f 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java @@ -53,7 +53,7 @@ public static String objectToString(Object value) throws JsonProcessingException "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" )) )); - static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681"; public static class createInstanceTest { @Rule From 3ec794f720c939c9e7bc2283ad3794dcd83c7300 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 10:16:29 +0900 Subject: [PATCH 34/89] Updated test cases Now storage keys are stored as ordered. --- .../klaytn/caver/common/transaction/EthereumAccessListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index a5ee2a27a..fb5fe5e58 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -1216,7 +1216,7 @@ public void getRLPEncodingForSignature() { .setAccessList(accessList) ); - String expected = "0x01f9011401040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7cf859940000000000000000000000000000000000000003f842a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc"; + String expected = "0x01f9011401040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681"; String rlpEncodedSign = ethereumAccessList.getRLPEncodingForSignature(); assertEquals(expected, rlpEncodedSign); From eabd2a0ec1b927b1cb5286393f55ab34a7e7b6ce Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 11:38:06 +0900 Subject: [PATCH 35/89] implemented ecsign() of PrivateKey class. --- .../caver/wallet/keyring/PrivateKey.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java index 0e62c0c18..6d92d6a22 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java @@ -86,12 +86,34 @@ public static PrivateKey generate(String entropy) { * @return SignatureData */ public SignatureData sign(String sigHash, int chainId) { + SignatureData signData = ecsign(sigHash); + signData.makeEIP155Signature(chainId); + + return signData; + } + + /** + * Signs with hashed data and returns signature data.

+ * It returns a signature which has v as a parity of the y value(0 for even, 1 for odd) of secp256k1 signature. + *

Example :
+     * {@code
+     * String hash = "0x{hash}";
+     * PrivateKey prvKey = new PrivateKey("{privateKeyString});
+     *
+     * SignatureData sign = prvKey.ecsign(hash);
+     * }
+     * 
+ * @param sigHash The hash to sign + * @return SignatureData + */ + public SignatureData ecsign(String sigHash) { ECKeyPair keyPair = ECKeyPair.create(Numeric.toBigInt(privateKey)); Sign.SignatureData signatureData = Sign.signMessage(Numeric.hexStringToByteArray(sigHash), keyPair, false); - SignatureData signData = new SignatureData(signatureData.getV(), signatureData.getR(), signatureData.getS()); - signData.makeEIP155Signature(chainId); + // Sign.signMessage() always add to 27 at V value. so it need to substract 27 from V value. + byte[] v = new byte[] {(byte)(signatureData.getV()[0] - 27)}; + SignatureData signData = new SignatureData(v, signatureData.getR(), signatureData.getS()); return signData; } From 69b9bf01ca4365f8d682a9619dcf93a1e41df7af Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 11:38:39 +0900 Subject: [PATCH 36/89] define ensign() methods in AbstractKeyring --- .../caver/wallet/keyring/AbstractKeyring.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java index 30594567a..9676f962e 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java @@ -55,6 +55,23 @@ public AbstractKeyring(String address) { */ abstract public SignatureData sign(String txHash, int chainId, int role, int index); + /** + * Signs a transaction hash with all private keys in specific role group and return signature list. + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return List<SignatureData> + */ + abstract public List ecsign(String txHash, int role); + + /** + * Signs a transaction hash with a private key in specific role group and return signature + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return SignatureData + */ + abstract public SignatureData ecsign(String txHash, int role, int index); + /** * Signs a hashed data with all private keys in specific role group and return MessageSigned instance. * @param message The data string to sign From 105f99b227cbb8c3b9b22a6172b758f7d44b3501 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 11:39:03 +0900 Subject: [PATCH 37/89] implemented ecsign() methods each Keyring class. --- .../caver/wallet/keyring/MultipleKeyring.java | 51 +++++++++++++++++++ .../wallet/keyring/RoleBasedKeyring.java | 50 ++++++++++++++++++ .../caver/wallet/keyring/SingleKeyring.java | 50 ++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java index 5f5d0be0b..41347c10d 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java @@ -82,6 +82,57 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return signatureData; } + /** + * Signs a transaction hash with all private keys in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * MultipleKeyring doesn't define the role group, so it signs a transaction using all keys existed in MultipleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * MultipleKeyring keyring = new MultipleKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey[] keyArr = getKeyByRole(role); + + return Arrays.stream(keyArr) + .map(key-> { + return key.ecsign(txHash); + }).collect(Collectors.toList()); + } + + /** + * Signs a transaction hash with key in specific role group and return signature. + * MultipleKeyring doesn't define the role group, so it signs a transaction using specific key existed in MultipleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *
Example :
+     * {@code
+     * MultipleKeyring keyring = new MultipleKeyring(.....);
+     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + validatedIndexWithKeys(index, this.keys.length); + + PrivateKey key = getKeyByRole(role)[index]; + SignatureData signatureData = key.ecsign(txHash); + + return signatureData; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java index 6664939ea..044be9d63 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java @@ -84,6 +84,56 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return signatureData; } + /** + * Signs a transaction hash with all private keys in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * RoleBasedKeyring keyring = new RoleBasedKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey[] keyArr = getKeyByRole(role); + + return Arrays.stream(keyArr) + .map(key-> { + return key.ecsign(txHash); + }).collect(Collectors.toList()); + } + + /** + * Signs a transaction hash with key in specific role group and return signature.

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * RoleBasedKeyring keyring = new RoleBasedKeyring(.....);
+     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + PrivateKey[] keyArr = getKeyByRole(role); + validatedIndexWithKeys(index, keyArr.length); + + PrivateKey key = keyArr[index]; + SignatureData signatureData = key.ecsign(txHash); + + return signatureData; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java index 6c3cda645..a860e47ab 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java @@ -78,6 +78,56 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return data; } + /** + * Signs a transaction hash with private key in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * SingleKeyring doesn't define the role group, so it signs a transaction using key existed in SingleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * SingleKeyring keyring = new SingleKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey key = getKeyByRole(role); + SignatureData data = key.ecsign(txHash); + + return Arrays.asList(data); + } + + /** + * Signs a transaction hash with private key in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * SingleKeyring doesn't define the role group and only have one private key, so it signs a transaction using key existed in SingleKeyring.

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}.

+ * The index value must 0. + *

Example :
+     * {@code
+     * SingleKeyring keyring = new SingleKeyring(.....);
+     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + validatedIndexWithKeys(index, 1); + + PrivateKey key = getKeyByRole(role); + SignatureData data = key.ecsign(txHash); + + return data; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign From 43470e00ca9b79f06fb7071ecbeb0229ca16aa37 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 11:39:14 +0900 Subject: [PATCH 38/89] added test --- .../caver/common/wallet/KeyringTest.java | 331 +++++++++++++++++- 1 file changed, 325 insertions(+), 6 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java b/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java index 45f399431..9fef507be 100644 --- a/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java +++ b/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java @@ -885,9 +885,9 @@ public void multipleKey() { assertEquals(3, actualList.size()); for(int i=0; i expected, List actual) { + assertEquals(expected.size(), actual.size()); + + for(int i=0; i signatureDataList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + assertEquals(1, signatureDataList.size()); + assertNotNull(signatureDataList.get(0).getR()); + assertNotNull(signatureDataList.get(0).getS()); + assertNotNull(signatureDataList.get(0).getV()); + assertTrue(Numeric.toBigInt(signatureDataList.get(0).getV()).compareTo(BigInteger.ONE) <= 0); + } + + @Test + public void coupleKey_With_NotExistedRole() { + AbstractKeyring keyring = KeyringFactory.generate(); + List expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(1, actualList.size()); + assertNotNull(actualList.get(0).getR()); + assertNotNull(actualList.get(0).getS()); + assertNotNull(actualList.get(0).getV()); + + checkSignature(expectedList, actualList); + } + + @Test + public void deCoupleKey() { + String address = PrivateKey.generate().getDerivedAddress(); + String privateKey = PrivateKey.generate().getPrivateKey(); + AbstractKeyring keyring = KeyringFactory.create(address, privateKey); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(1, actualList.size()); + assertNotNull(actualList.get(0).getR()); + assertNotNull(actualList.get(0).getS()); + assertNotNull(actualList.get(0).getV()); + assertTrue(Numeric.toBigInt(actualList.get(0).getV()).compareTo(BigInteger.ONE) <= 0); + } + + @Test + public void deCoupleKey_With_NotExistedRole() { + String address = PrivateKey.generate().getDerivedAddress(); + String privateKey = PrivateKey.generate().getPrivateKey(); + AbstractKeyring keyring = KeyringFactory.create(address, privateKey); + + List expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(1, actualList.size()); + checkSignature(expectedList, actualList); + } + + @Test + public void multipleKey() { + AbstractKeyring keyring = generateMultipleKeyring(3); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(3, actualList.size()); + + for(int i=0; i expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(3, actualList.size()); + checkSignature(expectedList, actualList); + } + + @Test + public void roleBasedKey() { + AbstractKeyring keyring = generateRoleBaseKeyring(new int[]{3,3,4}); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(3, actualList.size()); + + for(int i=0; i expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(3, actualList.size()); + checkSignature(expectedList, actualList); + } + } } From 67e317fb5352da13ce50c96829b5557dd6af7e88 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 12:55:13 +0900 Subject: [PATCH 39/89] Add AccessTupleWrapper --- .../caver/transaction/utils/AccessTuple.java | 11 +++- .../transaction/utils/TransactionUtils.java | 9 ++- .../utils/wrapper/AccessListWrapper.java | 2 +- .../utils/wrapper/AccessTupleWrapper.java | 55 +++++++++++++++++++ .../wrapper/TransactionWrapper.java | 3 +- .../transaction/utils/AccessTupleTest.java | 13 +++-- 6 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java index 7f50e4dff..19d598440 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -51,6 +51,15 @@ public AccessTuple(String address, List storageKeys) { this.setStorageKeys(storageKeys); } + /** + * Create an AccessTuple instance. + * @param address + * @param storageKeys + */ + public static AccessTuple create(String address, List storageKeys) { + return new AccessTuple(address, storageKeys); + } + /** * Getter function of address. * @return String @@ -96,7 +105,7 @@ public void setStorageKeys(List storageKeys) { /** * Decodes given RlpList to AccessTuple. - * @param rlpEncodedAccessTuple + * @param rlpEncodedAccessTuple RlpList representing rlp encoded access tuple. * @return */ public static AccessTuple decode(RlpList rlpEncodedAccessTuple) { diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java index a7235f924..fc0b84b9b 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java @@ -17,6 +17,7 @@ package com.klaytn.caver.transaction.utils; import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; +import com.klaytn.caver.transaction.utils.wrapper.AccessTupleWrapper; /** * TransactionUtils includes various helper classes for transaction packages. @@ -27,11 +28,17 @@ public class TransactionUtils { */ public AccessListWrapper accessList; + /** + * AccessTupleWrapper instance. + */ + public AccessTupleWrapper accessTuple; + /** * Creates a TransactionUtils instance. * @param accessList An instance of AccessListWrapper class. */ - public TransactionUtils(AccessListWrapper accessList) { + public TransactionUtils(AccessListWrapper accessList, AccessTupleWrapper accessTuple) { this.accessList = accessList; + this.accessTuple = accessTuple; } } diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java index 9291ec1f6..0d2521054 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java @@ -25,7 +25,7 @@ /** * Represents a AccessListWrapper * 1. This class wraps all of static methods of AccessList - * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + * 2. This class should be accessed via `caver.transaction.utils.accessList` */ public class AccessListWrapper { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java new file mode 100644 index 000000000..0c290233b --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.utils.wrapper; + +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.web3j.rlp.RlpList; + +import java.util.List; + +/** + * Represents a AccessTupleWrapper + * 1. This class wraps all of static methods of AccessTuple + * 2. This class should be accessed via `caver.transaction.utils.accessTuple` + */ +public class AccessTupleWrapper { + /** + * Creates a AccessTupleWrapper instance. + */ + public AccessTupleWrapper() { + } + + /** + * Creates an AccessTuple instance. + * @param address An address string. + * @param storageKeys A list of storage keys. + * @return + */ + public AccessTuple create(String address, List storageKeys) { + return AccessTuple.create(address, storageKeys); + } + + /** + * Decodes given RlpList to AccessTuple. + * + * @param rlpEncodedAccessTuple RlpList representing rlp encoded access tuple. + * @return AccessTuple + */ + public AccessTuple decode(RlpList rlpEncodedAccessTuple) { + return AccessTuple.decode(rlpEncodedAccessTuple); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index c1e4ebdf9..b72c6adb1 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -23,6 +23,7 @@ import com.klaytn.caver.transaction.type.wrapper.*; import com.klaytn.caver.transaction.utils.TransactionUtils; import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; +import com.klaytn.caver.transaction.utils.wrapper.AccessTupleWrapper; import java.util.List; @@ -187,7 +188,7 @@ public TransactionWrapper(Klay klaytnCall) { this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); - this.utils = new TransactionUtils(new AccessListWrapper()); + this.utils = new TransactionUtils(new AccessListWrapper(), new AccessTupleWrapper()); } /** diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java index 414d33bfe..a466885cc 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.utils.AccessTuple; import org.junit.Assert; import org.junit.Rule; @@ -38,13 +39,15 @@ public static String objectToString(Object value) throws JsonProcessingException return ow.writeValueAsString(value); } + static Caver caver = new Caver(); + public static class createInstanceTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void create() throws IOException { - AccessTuple expectedAccessTuple = new AccessTuple( + AccessTuple expectedAccessTuple = caver.transaction.utils.accessTuple.create( "0x4173c51bd0e64a4c58656a5401373a476e8534ec", Arrays.asList( "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", @@ -52,7 +55,7 @@ public void create() throws IOException { ) ); - AccessTuple accessTuple = new AccessTuple( + AccessTuple accessTuple = caver.transaction.utils.accessTuple.create( "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", Arrays.asList( "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", @@ -73,7 +76,7 @@ public void create_invalidAddress() { expectedException.expect(RuntimeException.class); expectedException.expectMessage("Invalid address. Address: " + invalidAddress); - new AccessTuple( + caver.transaction.utils.accessTuple.create( invalidAddress, Arrays.asList( "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", @@ -89,7 +92,7 @@ public void create_invalidStorageKey() { expectedException.expect(RuntimeException.class); expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey.toLowerCase()); - new AccessTuple( + caver.transaction.utils.accessTuple.create( "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", Arrays.asList( invalidStorageKey, @@ -100,7 +103,7 @@ public void create_invalidStorageKey() { @Test public void create_orderedKeys() { - AccessTuple accessTuple = new AccessTuple( + AccessTuple accessTuple = caver.transaction.utils.accessTuple.create( "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", Arrays.asList( "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717", From f2ac3621f32491a3ead9ffb031582ab847b41074 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 13:07:37 +0900 Subject: [PATCH 40/89] decode is used internally so it removed from Wrapper. --- .../transaction/utils/wrapper/AccessTupleWrapper.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java index 0c290233b..cded38450 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java @@ -42,14 +42,4 @@ public AccessTupleWrapper() { public AccessTuple create(String address, List storageKeys) { return AccessTuple.create(address, storageKeys); } - - /** - * Decodes given RlpList to AccessTuple. - * - * @param rlpEncodedAccessTuple RlpList representing rlp encoded access tuple. - * @return AccessTuple - */ - public AccessTuple decode(RlpList rlpEncodedAccessTuple) { - return AccessTuple.decode(rlpEncodedAccessTuple); - } } \ No newline at end of file From d97d290edb49f1b679aed468e44250aeda6da7c0 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 13:20:11 +0900 Subject: [PATCH 41/89] Add EtheruemAccessList wrapper This was missed during merge another branch onto this branch. --- .../caver/transaction/wrapper/TransactionWrapper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index b72c6adb1..9277e86f3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -146,6 +146,11 @@ public class TransactionWrapper { */ public FeeDelegatedChainDataAnchoringWithRatioWrapper feeDelegatedChainDataAnchoringWithRatio; + /** + * EthereumAccessListWrapper instance + */ + public EthereumAccessListWrapper ethereumAccessList; + /** * TransactionUtils instance */ @@ -188,6 +193,7 @@ public TransactionWrapper(Klay klaytnCall) { this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); + this.ethereumAccessList = new EthereumAccessListWrapper(klaytnCall); this.utils = new TransactionUtils(new AccessListWrapper(), new AccessTupleWrapper()); } From c8feb367678690cee61e1a4e849de52b2721bde9 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 13:40:48 +0900 Subject: [PATCH 42/89] resolve error --- .../java/com/klaytn/caver/wallet/keyring/PrivateKey.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java index 6d92d6a22..6b2becc3a 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java @@ -86,7 +86,10 @@ public static PrivateKey generate(String entropy) { * @return SignatureData */ public SignatureData sign(String sigHash, int chainId) { - SignatureData signData = ecsign(sigHash); + ECKeyPair keyPair = ECKeyPair.create(Numeric.toBigInt(privateKey)); + Sign.SignatureData signatureData = Sign.signMessage(Numeric.hexStringToByteArray(sigHash), keyPair, false); + + SignatureData signData = new SignatureData(v, signatureData.getR(), signatureData.getS()); signData.makeEIP155Signature(chainId); return signData; From e3e6a84d444dd37d9561305303b7d8520e2fc8fd Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Thu, 3 Mar 2022 13:42:49 +0900 Subject: [PATCH 43/89] resolve error --- .../main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java index 6b2becc3a..1aabf81aa 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java @@ -89,7 +89,7 @@ public SignatureData sign(String sigHash, int chainId) { ECKeyPair keyPair = ECKeyPair.create(Numeric.toBigInt(privateKey)); Sign.SignatureData signatureData = Sign.signMessage(Numeric.hexStringToByteArray(sigHash), keyPair, false); - SignatureData signData = new SignatureData(v, signatureData.getR(), signatureData.getS()); + SignatureData signData = new SignatureData(signatureData.getV(), signatureData.getR(), signatureData.getS()); signData.makeEIP155Signature(chainId); return signData; From 4bb3e284d07e627d54271cd9238742dd8fb2532d Mon Sep 17 00:00:00 2001 From: kale Date: Thu, 3 Mar 2022 14:16:53 +0900 Subject: [PATCH 44/89] Apply suggestions from code review Co-authored-by: Jamie <32922423+jimni1222@users.noreply.github.com> --- .../java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java | 4 ++-- .../java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java | 4 ++-- .../com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java | 4 ++-- .../java/com/klaytn/caver/wallet/keyring/SingleKeyring.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java index 9676f962e..a87668ec5 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java @@ -56,7 +56,7 @@ public AbstractKeyring(String address) { abstract public SignatureData sign(String txHash, int chainId, int role, int index); /** - * Signs a transaction hash with all private keys in specific role group and return signature list. + * Signs a transaction hash with all private keys in specific role group and returns signature list. * @param txHash The hash of transaction. * @param role A number indicating the role of the key. * @return List<SignatureData> @@ -64,7 +64,7 @@ public AbstractKeyring(String address) { abstract public List ecsign(String txHash, int role); /** - * Signs a transaction hash with a private key in specific role group and return signature + * Signs a transaction hash with a private key in specific role group and returns signature * @param txHash The hash transaction * @param role A number indicating the role of the key. * @param index The index of the key to be used in the specific role group. diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java index 41347c10d..9d1c21942 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java @@ -83,7 +83,7 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { } /** - * Signs a transaction hash with all private keys in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * Signs a transaction hash with all private keys in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

* MultipleKeyring doesn't define the role group, so it signs a transaction using all keys existed in MultipleKeyring. * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. *

Example :
@@ -108,7 +108,7 @@ public List ecsign(String txHash, int role) {
     }
 
     /**
-     * Signs a transaction hash with key in specific role group and return signature.
+     * Signs a transaction hash with key in specific role group and returns a signature.
      * MultipleKeyring doesn't define the role group, so it signs a transaction using specific key existed in MultipleKeyring.
      * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}.
      * 
Example :
diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
index 044be9d63..a3b0db8d1 100644
--- a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
+++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
@@ -85,7 +85,7 @@ public SignatureData sign(String txHash, int chainId, int role, int index) {
     }
 
     /**
-     * Signs a transaction hash with all private keys in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature). 

+ * Signs a transaction hash with all private keys in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

* The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. *

Example :
      * {@code
@@ -109,7 +109,7 @@ public List ecsign(String txHash, int role) {
     }
 
     /**
-     * Signs a transaction hash with key in specific role group and return signature.

+ * Signs a transaction hash with key in specific role group and returns a signature.

* The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. *

Example :
      * {@code
diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java
index a860e47ab..6bd0c74fc 100644
--- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java
+++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java
@@ -79,7 +79,7 @@ public SignatureData sign(String txHash, int chainId, int role, int index) {
     }
 
     /**
-     * Signs a transaction hash with private key in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature). 

+ * Signs a transaction hash with private key in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

* SingleKeyring doesn't define the role group, so it signs a transaction using key existed in SingleKeyring. * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. *

Example :
@@ -102,7 +102,7 @@ public List ecsign(String txHash, int role) {
     }
 
     /**
-     * Signs a transaction hash with private key in specific role group and return signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature). 

+ * Signs a transaction hash with private key in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

* SingleKeyring doesn't define the role group and only have one private key, so it signs a transaction using key existed in SingleKeyring.

* The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}.

* The index value must 0. From 89d42b5d95743a2c83e4c696b6acccb6e5d26558 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 15:01:19 +0900 Subject: [PATCH 45/89] Update newly added klay apis and FeeHistoryResult --- .../methods/response/FeeHistoryResult.java | 6 +-- .../main/java/com/klaytn/caver/rpc/Klay.java | 52 +++++++++++-------- .../com/klaytn/caver/common/rpc/RpcTest.java | 13 +++-- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java index 082e624ce..2cdd52816 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java @@ -47,7 +47,7 @@ public static class FeeHistoryResultData { /** * An array of gasUsed/gasLimit in the block. */ - private List gasUsedRatio; + private List gasUsedRatio; public String getOldestBlock() { return oldestBlock; @@ -73,11 +73,11 @@ public void setReward(List> reward) { this.reward = reward; } - public List getGasUsedRatio() { + public List getGasUsedRatio() { return gasUsedRatio; } - public void setGasUsedRatio(List gasUsedRatio) { + public void setGasUsedRatio(List gasUsedRatio) { this.gasUsedRatio = gasUsedRatio; } } diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index aae1e7b28..296336ea5 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -34,6 +34,7 @@ import org.web3j.protocol.core.DefaultBlockParameterNumber; import org.web3j.protocol.core.Request; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -431,19 +432,16 @@ public Request getHeaderByHash(String blockHash) { * Returns a block header by block number. *

Example :
      * {@code
-     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(5);
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
      * }
      * 
* @param blockNumber The block number. * @return BlockHeader */ - public Request getHeaderByNumber(long blockNumber) { - return new Request<>( - "klay_getHeaderByNumber", - Arrays.asList(blockNumber), - web3jService, - BlockHeader.class); + public Request getHeaderByNumber(BigInteger blockNumber) { + DefaultBlockParameterNumber blockParameterNumber = new DefaultBlockParameterNumber(blockNumber); + return getHeaderByNumber(blockParameterNumber); } /** @@ -452,15 +450,18 @@ public Request getHeaderByNumber(long blockNumber) { * {@code * BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST); * BlockHeader.BlockHeaderData blockHeaderData = response.getResult(); + * + * response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0)); + * blockHeaderData = response.getResult(); * } *
- * @param defaultBlockParameter The string "latest", "earliest", or "pending". + * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". * @return BlockHeader */ - public Request getHeaderByNumber(DefaultBlockParameter defaultBlockParameter) { + public Request getHeaderByNumber(DefaultBlockParameter blockNumberOrTag) { return new Request<>( "klay_getHeaderByNumber", - Arrays.asList(defaultBlockParameter), + Arrays.asList(blockNumberOrTag), web3jService, BlockHeader.class); } @@ -485,14 +486,14 @@ public Request getHeader(String blockHash) { * Returns a block header by block number. *
Example :
      * {@code
-     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(5);
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
      * }
      * 
* @param blockNumber The block number. * @return BlockHeader */ - public Request getHeader(long blockNumber) { + public Request getHeader(BigInteger blockNumber) { return getHeaderByNumber(blockNumber); } @@ -502,13 +503,16 @@ public Request getHeader(long blockNumber) { * {@code * BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST); * BlockHeader.BlockHeaderData blockHeaderData = response.getResult(); + * + * response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0)); + * blockHeaderData = response.getResult(); * } *
- * @param blockTag The string "latest", "earliest", or "pending". + * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". * @return BlockHeader */ - public Request getHeader(DefaultBlockParameter blockTag) { - return getHeaderByNumber(blockTag); + public Request getHeader(DefaultBlockParameter blockNumberOrTag) { + return getHeaderByNumber(blockNumberOrTag); } /** @@ -1454,19 +1458,21 @@ public Request sha3(String data) { * * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send(); * + * accessListResult = caver.rpc.klay.createAccessList(callObject, new DefaultBlockParameterNumber(1)).send(); + * * } *
* @param callObject The transaction call object. - * @param defaultBlockParameter A block number, or the block tag string `latest` or `earliest`. If omitted, `latest` will be used. + * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". * @return AccessListResult */ - public Request createAccessList(CallObject callObject, DefaultBlockParameter defaultBlockParameter) { - if (defaultBlockParameter == null) { - defaultBlockParameter = DefaultBlockParameterName.LATEST; + public Request createAccessList(CallObject callObject, DefaultBlockParameter blockNumberOrTag) { + if (blockNumberOrTag == null) { + blockNumberOrTag = DefaultBlockParameterName.LATEST; } return new Request<>( "klay_createAccessList", - Arrays.asList(callObject, defaultBlockParameter), + Arrays.asList(callObject, blockNumberOrTag), web3jService, AccessListResult.class ); @@ -1500,7 +1506,7 @@ public Request createAccessList(CallObject callObject, Stri *
      * {@code
      *
-     * long blockNumber = 5;
+     * BigInteger blockNumber = BigInteger.valueOf(5);
      * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
      *
      * }
@@ -1509,10 +1515,10 @@ public Request createAccessList(CallObject callObject, Stri
      * @param blockNumber The block number.
      * @return AccessListResult
      */
-    public Request createAccessList(CallObject callObject, long blockNumber) {
+    public Request createAccessList(CallObject callObject, BigInteger blockNumber) {
         return new Request<>(
                 "klay_createAccessList",
-                Arrays.asList(callObject, blockNumber),
+                Arrays.asList(callObject, new DefaultBlockParameterNumber(blockNumber)),
                 web3jService,
                 AccessListResult.class
         );
diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java
index 5f9921eec..5ddae1ea3 100644
--- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java
+++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java
@@ -600,7 +600,7 @@ public void getHeaderTest() {
                 String hash = blockHeader.getHash();
                 assertNotNull(hash);
 
-                response = caver.rpc.klay.getHeader(0).send();
+                response = caver.rpc.klay.getHeader(BigInteger.valueOf(0)).send();
                 blockHeader = response.getResult();
                 assertNotNull(blockHeader.getHash());
 
@@ -620,7 +620,11 @@ public void getHeaderByNumberTest() {
                 BlockHeader.BlockHeaderData blockHeader = response.getResult();
                 assertNotNull(blockHeader.getHash());
 
-                response = caver.rpc.klay.getHeaderByNumber(0).send();
+                response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0)).send();
+                blockHeader = response.getResult();
+                assertNotNull(blockHeader.getHash());
+
+                response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(0)).send();
                 blockHeader = response.getResult();
                 assertNotNull(blockHeader.getHash());
             } catch (Exception e) {
@@ -974,7 +978,7 @@ public void getFeeHistoryTest() throws IOException, InterruptedException {
         public void createAccessListTest() throws IOException {
             Block block = caver.rpc.klay.getBlockByNumber(DefaultBlockParameterName.LATEST).send();
             Quantity gasPrice = caver.rpc.klay.getGasPrice().send();
-            long blockNumber = new BigInteger(caver.utils.stripHexPrefix(block.getResult().getNumber()), 16).longValue();
+            BigInteger blockNumber = new BigInteger(caver.utils.stripHexPrefix(block.getResult().getNumber()), 16);
             String blockHash = block.getResult().getHash();
             CallObject callObject = CallObject.createCallObject(
                     LUMAN.getAddress(),
@@ -986,6 +990,9 @@ public void createAccessListTest() throws IOException {
             AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
             checkAccessListResult(accessListResult.getResult());
 
+            accessListResult = caver.rpc.klay.createAccessList(callObject, new DefaultBlockParameterNumber(blockNumber)).send();
+            checkAccessListResult(accessListResult.getResult());
+
             accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
             checkAccessListResult(accessListResult.getResult());
 

From 644c08a66a6f746c8ff47ca1cdd1b03683b72af6 Mon Sep 17 00:00:00 2001
From: Denver 
Date: Thu, 3 Mar 2022 15:23:33 +0900
Subject: [PATCH 46/89] Add method to accept blockTag

---
 .../main/java/com/klaytn/caver/rpc/Klay.java  | 52 ++++++++++++++++++-
 1 file changed, 50 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
index 296336ea5..876082e4f 100644
--- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java
+++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
@@ -445,7 +445,22 @@ public Request getHeaderByNumber(BigInteger blockNumber) {
     }
 
     /**
-     * Returns a block header by block number.
+     * Returns a block header by block tag.
+     * 
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockTag The string "latest", "earliest" or "pending" + * @return BlockHeader + */ + public Request getHeaderByNumber(DefaultBlockParameterName blockTag) { + return getHeaderByNumber(DefaultBlockParameter.valueOf(blockTag.getValue())); + } + + /** + * Returns a block header by block number or block tag. *
Example :
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
@@ -498,7 +513,22 @@ public Request getHeader(BigInteger blockNumber) {
     }
 
     /**
-     * Returns a block header by block number.
+     * Returns a block header by block tag.
+     * 
Example :
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeader(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockTag The string "latest", "earliest" or "pending" + * @return BlockHeader + */ + public Request getHeader(DefaultBlockParameterName blockTag) { + return getHeaderByNumber(DefaultBlockParameter.valueOf(blockTag.getValue())); + } + + /** + * Returns a block header by block number or block tag. *
Example :
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
@@ -1451,6 +1481,24 @@ public Request sha3(String data) {
                 Bytes.class);
     }
 
+
+    /**
+     * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
+     * 
+     * {@code
+     *
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockTag The string "latest", "earliest" or "pending" + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, DefaultBlockParameterName blockTag) { + return createAccessList(callObject, DefaultBlockParameter.valueOf(blockTag.getValue())); + } + /** * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. *

From 3824e443c505cc76627f575004582a7856753a6d Mon Sep 17 00:00:00 2001
From: Denver 
Date: Thu, 3 Mar 2022 15:42:44 +0900
Subject: [PATCH 47/89] Call already defined method accepting
 DefaultBlockParameter

---
 .../main/java/com/klaytn/caver/rpc/Klay.java  | 40 +++++++++----------
 1 file changed, 18 insertions(+), 22 deletions(-)

diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
index 876082e4f..c9c25be03 100644
--- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java
+++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
@@ -1482,6 +1482,24 @@ public Request sha3(String data) {
     }
 
 
+    /**
+     * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
+     * 
+     * {@code
+     *
+     * BigInteger blockNumber = BigInteger.valueOf(5);
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockNumber The block number. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, BigInteger blockNumber) { + return createAccessList(callObject, new DefaultBlockParameterNumber(blockNumber)); + } + /** * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. *
@@ -1549,28 +1567,6 @@ public Request createAccessList(CallObject callObject, Stri
         );
     }
 
-    /**
-     * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
-     * {@code
-     *
-     * BigInteger blockNumber = BigInteger.valueOf(5);
-     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
-     *
-     * }
-     * 
- * @param callObject The transaction call object. - * @param blockNumber The block number. - * @return AccessListResult - */ - public Request createAccessList(CallObject callObject, BigInteger blockNumber) { - return new Request<>( - "klay_createAccessList", - Arrays.asList(callObject, new DefaultBlockParameterNumber(blockNumber)), - web3jService, - AccessListResult.class - ); - } /** * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. From dc25b4eb68485ab8d361cf4069d6fcef19badfd9 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 15:47:30 +0900 Subject: [PATCH 48/89] Apply suggestions from code review Co-authored-by: kale --- core/src/main/java/com/klaytn/caver/rpc/Klay.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index c9c25be03..06b781389 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -1279,7 +1279,7 @@ public Request getGasPriceAt() { /** * Returns a suggestion for a gas tip cap for dynamic fee transactions in peb.

* Note: Since Klaytn has a fixed gas price, this `caver.rpc.klay.getMaxPriorityFeePerGas` returns the gas price set by Klaytn. - *

+     * 
 Example : 
      * {@code
      *
      * Quantity response = caver.rpc.klay.getMaxPriorityFeePerGas().send();
@@ -1484,7 +1484,7 @@ public Request sha3(String data) {
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
+     * 
 Example : 
      * {@code
      *
      * BigInteger blockNumber = BigInteger.valueOf(5);
@@ -1519,7 +1519,7 @@ public Request createAccessList(CallObject callObject, Defau
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
+     * 
 Example : 
      * {@code
      *
      * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();

From 8c35cdfc141c068d18c2b3e29e213e5edfe8378d Mon Sep 17 00:00:00 2001
From: Denver 
Date: Thu, 3 Mar 2022 15:57:15 +0900
Subject: [PATCH 49/89] Add "Example" statement at javadoc

---
 .../main/java/com/klaytn/caver/rpc/Klay.java  | 28 +++++++++----------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
index 06b781389..f992b05b4 100644
--- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java
+++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java
@@ -410,7 +410,7 @@ public Request getBlockNumber() {
 
     /**
      * Returns a block header by block hash.
-     * 
Example :
+     * 
Example:
      * {@code
      *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
      *  BlockHeader response = caver.rpc.klay.getHeaderByHash(blockHash);
@@ -430,7 +430,7 @@ public Request getHeaderByHash(String blockHash) {
 
     /**
      * Returns a block header by block number.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -446,7 +446,7 @@ public Request getHeaderByNumber(BigInteger blockNumber) {
 
     /**
      * Returns a block header by block tag.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -461,7 +461,7 @@ public Request getHeaderByNumber(DefaultBlockParameterName block
 
     /**
      * Returns a block header by block number or block tag.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -483,7 +483,7 @@ public Request getHeaderByNumber(DefaultBlockParameter blockNumb
 
     /**
      * Returns a block header by block hash.
-     * 
Example :
+     * 
Example:
      * {@code
      *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
      *  BlockHeader response = caver.rpc.klay.getHeader(blockHash);
@@ -499,7 +499,7 @@ public Request getHeader(String blockHash) {
 
     /**
      * Returns a block header by block number.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -514,7 +514,7 @@ public Request getHeader(BigInteger blockNumber) {
 
     /**
      * Returns a block header by block tag.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeader(DefaultBlockParameterName.LATEST);
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -529,7 +529,7 @@ public Request getHeader(DefaultBlockParameterName blockTag) {
 
     /**
      * Returns a block header by block number or block tag.
-     * 
Example :
+     * 
Example:
      * {@code
      *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
      *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
@@ -1484,7 +1484,7 @@ public Request sha3(String data) {
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
 Example : 
+     * 
Example :
      * {@code
      *
      * BigInteger blockNumber = BigInteger.valueOf(5);
@@ -1502,7 +1502,7 @@ public Request createAccessList(CallObject callObject, BigI
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
+     * 
Example:
      * {@code
      *
      * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
@@ -1519,7 +1519,7 @@ public Request createAccessList(CallObject callObject, Defau
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
 Example : 
+     * 
Example:
      * {@code
      *
      * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
@@ -1546,7 +1546,7 @@ public Request createAccessList(CallObject callObject, Defau
 
     /**
      * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added.
-     * 
+     * 
Example:
      * {@code
      *
      * String blockHash = "0x421440aef6024e2da883eadf663b9b485fe1c14f02883541fa4e6c16f7be8c74";
@@ -1570,7 +1570,7 @@ public Request createAccessList(CallObject callObject, Stri
 
     /**
      * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.
-     * 
Example :
+     * 
Example:
      * {@code
      *
      * long blockCount = 5;
@@ -1596,7 +1596,7 @@ public Request getFeeHistory(long blockCount, long lastBloc
 
     /**
      * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.
-     * 
Example :
+     * 
Example:
      * {@code
      *
      * long blockCount = 5;

From 81e7a7c3ae1d107bddf3c08589591945204f2c27 Mon Sep 17 00:00:00 2001
From: Denver 
Date: Thu, 3 Mar 2022 16:38:56 +0900
Subject: [PATCH 50/89] Apply feedback from kale

1> make TransactionUtils as TransactionUtilsWrapper
This is a just wrapping claas, so it should be renamed and placed as wrapper.
2> change constructor of TransactionUtilsWrapper
Because there is no configuration about AccessListWrapper and AccessTupleWrapper, it should keep constructor as empty.
3> change estimating test cases to check null instead of exact value.
---
 .../TransactionUtilsWrapper.java}               | 17 +++++++----------
 .../transaction/wrapper/TransactionWrapper.java |  8 +++-----
 .../klaytn/caver/legacy/feature/RpcTest.java    |  4 ++--
 3 files changed, 12 insertions(+), 17 deletions(-)
 rename core/src/main/java/com/klaytn/caver/transaction/utils/{TransactionUtils.java => wrapper/TransactionUtilsWrapper.java} (62%)

diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java
similarity index 62%
rename from core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java
rename to core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java
index fc0b84b9b..3d17e4b38 100644
--- a/core/src/main/java/com/klaytn/caver/transaction/utils/TransactionUtils.java
+++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.klaytn.caver.transaction.utils;
-
-import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper;
-import com.klaytn.caver.transaction.utils.wrapper.AccessTupleWrapper;
+package com.klaytn.caver.transaction.utils.wrapper;
 
 /**
- * TransactionUtils includes various helper classes for transaction packages.
+ * TransactionUtilsWrapper provide usability so that the static method of the access list and the static method of the access tuple

+ * can be accessed and used through `caver.transaction.utils`. */ -public class TransactionUtils { +public class TransactionUtilsWrapper { /** * AccessListWrapper instance. */ @@ -35,10 +33,9 @@ public class TransactionUtils { /** * Creates a TransactionUtils instance. - * @param accessList An instance of AccessListWrapper class. */ - public TransactionUtils(AccessListWrapper accessList, AccessTupleWrapper accessTuple) { - this.accessList = accessList; - this.accessTuple = accessTuple; + public TransactionUtilsWrapper() { + this.accessList = new AccessListWrapper(); + this.accessTuple = new AccessTupleWrapper(); } } diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index b72c6adb1..b22954bb4 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -21,9 +21,7 @@ import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.type.wrapper.*; -import com.klaytn.caver.transaction.utils.TransactionUtils; -import com.klaytn.caver.transaction.utils.wrapper.AccessListWrapper; -import com.klaytn.caver.transaction.utils.wrapper.AccessTupleWrapper; +import com.klaytn.caver.transaction.utils.wrapper.TransactionUtilsWrapper; import java.util.List; @@ -149,7 +147,7 @@ public class TransactionWrapper { /** * TransactionUtils instance */ - public TransactionUtils utils; + public TransactionUtilsWrapper utils; /** * Creates a Transaction instance @@ -188,7 +186,7 @@ public TransactionWrapper(Klay klaytnCall) { this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); - this.utils = new TransactionUtils(new AccessListWrapper(), new AccessTupleWrapper()); + this.utils = new TransactionUtilsWrapper(); } /** diff --git a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java index f441c1e5f..42ba3bdd4 100644 --- a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java @@ -337,7 +337,7 @@ public void testEstimateGas() throws Exception { ); Quantity response = caver.klay().estimateGas(callObject).send(); String result = response.getResult(); - assertEquals("0x5398", result); + assertNotNull(result); } @Test @@ -353,7 +353,7 @@ public void testEstimateComputationCost() throws Exception { ); Quantity response = caver.klay().estimateComputationCost(callObject, DefaultBlockParameterName.LATEST).send(); String result = response.getResult(); - assertEquals("0x5318", result); + assertNotNull(result); } @Test From 104ae3eea590c58b940efccac72f431a14e4cefa Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 16:56:04 +0900 Subject: [PATCH 51/89] Apply suggestions from code review Co-authored-by: Jamie <32922423+jimni1222@users.noreply.github.com> --- .../java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java | 2 +- .../main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java | 2 +- .../java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java | 2 +- .../java/com/klaytn/caver/wallet/keyring/SingleKeyring.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java index 9d1c21942..380c6c776 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java @@ -114,7 +114,7 @@ public List ecsign(String txHash, int role) { *

Example :
      * {@code
      * MultipleKeyring keyring = new MultipleKeyring(.....);
-     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
      * }
      * 
* diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java index 1aabf81aa..a28013a7d 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java @@ -101,7 +101,7 @@ public SignatureData sign(String sigHash, int chainId) { *
Example :
      * {@code
      * String hash = "0x{hash}";
-     * PrivateKey prvKey = new PrivateKey("{privateKeyString});
+     * PrivateKey prvKey = new PrivateKey("{privateKeyString}");
      *
      * SignatureData sign = prvKey.ecsign(hash);
      * }
diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
index a3b0db8d1..35b405997 100644
--- a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
+++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java
@@ -114,7 +114,7 @@ public List ecsign(String txHash, int role) {
      * 
Example :
      * {@code
      * RoleBasedKeyring keyring = new RoleBasedKeyring(.....);
-     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
      * }
      * 
* diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java index 6bd0c74fc..dbebf0c14 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java @@ -109,7 +109,7 @@ public List ecsign(String txHash, int role) { *
Example :
      * {@code
      * SingleKeyring keyring = new SingleKeyring(.....);
-     * SignatureData signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
      * }
      * 
* From 3ae240ee184bb9e9e8d6c2fa01d0df0e78cdd11d Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 22:30:52 +0900 Subject: [PATCH 52/89] Override all sign methods of AbstractTransaction EthereumAccessList should use PrivateKey.ecsign method internally. Add test scenarios about signing. --- .../transaction/type/EthereumAccessList.java | 78 +++++ .../transaction/EthereumAccessListTest.java | 283 +++++++++++++++++- 2 files changed, 359 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 0d81fdf70..7c83546b8 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -16,20 +16,26 @@ package com.klaytn.caver.transaction.type; +import com.klaytn.caver.account.AccountKeyRoleBased; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.KeyringFactory; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Function; /** * Represents an ethereum access list transaction. @@ -356,6 +362,78 @@ public String getTransactionHash() { return Hash.sha3(Numeric.toHexString(detachedType)); } + @Override + public AbstractTransaction sign(String keyString) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + @Override + public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { + if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + String from = getFrom(); + if(from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS) || from == null){ + this.setFrom(keyring.getAddress()); + } + + if(!from.toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + List sigList = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + this.appendSignatures(sigList); + + return this; + } + + @Override + public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + @Override + public AbstractTransaction sign(String keyString, Function signer) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, signer); + } + + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOException { + return this.sign(keyring, index, TransactionHasher::getHashForSignature); + } + + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { + if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + String from = getFrom(); + if(from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS) || from == null){ + this.setFrom(keyring.getAddress()); + } + + if(!from.toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + SignatureData signatureData = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex(), index); + + this.appendSignatures(signatureData); + + return this; + } + /** * Getter function for to * diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index fb5fe5e58..c486a52e8 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -17,7 +17,9 @@ package com.klaytn.caver.common.transaction; import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; +import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.type.EthereumAccessList; import com.klaytn.caver.transaction.type.TransactionType; @@ -25,8 +27,7 @@ import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.wallet.keyring.AbstractKeyring; import com.klaytn.caver.wallet.keyring.SignatureData; -import org.junit.Rule; -import org.junit.Test; +import org.junit.*; import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @@ -1279,4 +1280,282 @@ public void throwException_NotDefined_ChainID() { } } + public static class signWithKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x1"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c", + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681" + )) + )); + + public static EthereumAccessList createEthereumAccessList() { + return caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setInput(input) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0xbf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7f", + "64bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8" + ); + static String expectedRlpEncoded = "0x7801f9015701040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d870186616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a0bf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7fa064bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKey_Keyring() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, 0, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoIndex() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoSigner() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, 0); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_Only() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_KeyString_NoIndex() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("TxTypeEthereumAccessList cannot be signed with a decoupled keyring."); + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + ethereumAccessList.sign(deCoupledKeyring); + } + + @Test + public void throwException_notEqualAddress() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The from address of the transaction is different with the address of the keyring to use"); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(Numeric.toBigInt(gas)) + .setGasPrice(Numeric.toBigInt(gasPrice)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setFrom("0x7b65b75d204abed71587c9e519a89277766aaaa1") + .setTo(to) + .setAccessList(accessList) + ); + + ethereumAccessList.sign(coupledKeyring); + } + } + + public static class signWithKeysTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x1"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c", + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681" + )) + )); + + public static EthereumAccessList createEthereumAccessList() { + return caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setInput(input) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0xbf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7f", + "64bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8" + ); + static String expectedRlpEncoded = "0x7801f9015701040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d870186616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a0bf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7fa064bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKeys_KeyString() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKeys_KeyString_KlaytnWalletKeyFormat() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.createFromPrivateKey(privateKey).getAddress(); + AbstractTransaction tx = ethereumAccessList.sign(klaytnKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.generate().getAddress(); + ethereumAccessList.sign(klaytnKey); + } + + @Test + public void throwException_multipleKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + String[] privateKeyArr = { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }; + + AbstractKeyring keyring = caver.wallet.keyring.createWithMultipleKey( + caver.wallet.keyring.generate().getAddress(), + privateKeyArr + ); + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + ethereumAccessList.sign(keyring); + } + + @Test + public void throwException_roleBasedKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + String[][] privateKeyArr = { + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + } + }; + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + + AbstractKeyring keyring = caver.wallet.keyring.createWithRoleBasedKey( + caver.wallet.keyring.generate().getAddress(), + Arrays.asList(privateKeyArr) + ); + ethereumAccessList.sign(keyring); + } + } + } From d8276a287c29bc8af94091db1b13e5076c937666 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 22:34:29 +0900 Subject: [PATCH 53/89] Refactor TransactionDecoder to caver.transaction.decoder --- .../caver/common/transaction/EthereumAccessListTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index c486a52e8..8fb1b38be 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -18,7 +18,6 @@ import com.klaytn.caver.Caver; import com.klaytn.caver.transaction.AbstractTransaction; -import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.type.EthereumAccessList; @@ -396,7 +395,7 @@ public void getRLPEncodingAndDecoding() { ); assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); // Test-1: decoding - EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-2: encoding EthereumAccessList which have many storageKeys @@ -414,7 +413,7 @@ public void getRLPEncodingAndDecoding() { ); assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); // Test-2: decoding - decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-3: encoding EthereumAccessList which have empty accessList @@ -433,7 +432,7 @@ public void getRLPEncodingAndDecoding() { ); assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); // Test-3: decoding - decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // Test-4: encoding EthereumAccessList which have many accessList @@ -450,7 +449,7 @@ public void getRLPEncodingAndDecoding() { ); assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); // Test-4: decoding - decodedEthereumAccessList = (EthereumAccessList) TransactionDecoder.decode(expectedRLP); + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); } From be0a5f7bb86e689f1af9e7ba7c335a7add23d763 Mon Sep 17 00:00:00 2001 From: Denver Date: Thu, 3 Mar 2022 23:17:59 +0900 Subject: [PATCH 54/89] Fix errors String.isEmpty must be used when try to compare String is empty or null. --- .../caver/transaction/type/EthereumAccessList.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 7c83546b8..6f27c465c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -374,12 +374,11 @@ public AbstractTransaction sign(AbstractKeyring keyring, Function Date: Fri, 4 Mar 2022 00:03:33 +0900 Subject: [PATCH 55/89] Update signature rlp encoding logic. Cover when given v value is 0. It must be treated as integer 0. --- .../caver/wallet/keyring/SignatureData.java | 11 +++- .../transaction/EthereumAccessListTest.java | 59 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java index 9b5186eb6..025efacb2 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java @@ -160,8 +160,17 @@ public RlpList toRlpList() { byte[] r = Numeric.hexStringToByteArray(getR()); byte[] s = Numeric.hexStringToByteArray(getS()); + RlpString rlpV; + byte[] trimmedV = Bytes.trimLeadingZeroes(v); + if (trimmedV[0] == 0) { + // When given v value is "0x0", it must be treated as integer 0 by encoding rule. + rlpV = RlpString.create(BigInteger.valueOf(0)); + } else { + rlpV = RlpString.create(trimmedV); + } + return new RlpList( - RlpString.create(Bytes.trimLeadingZeroes(v)), + rlpV, RlpString.create(Bytes.trimLeadingZeroes(r)), RlpString.create(Bytes.trimLeadingZeroes(s)) ); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 8fb1b38be..349cfdfa2 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -453,6 +453,65 @@ public void getRLPEncodingAndDecoding() { assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); } + @Test + public void getRLPEncodingYParity() { + // 0 y-parity + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo("0xc6779d72a88bec1a03bbb83cf028d95ff5f32f5b") + .setValue("0x1") + .setGas("0x9c40") + .setNonce("0x1a") + .setGasPrice("0x5d21dba00") + .setChainId("0x2710") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create(Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0xac9ba2a7fb8572e971bcac01a5b58934b385a172", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ))) + .setSignatures(new SignatureData( + "0x00", + "0x43ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848", + "0x17a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af" + )) + ); + + Assert.assertEquals("0x7801f901098227101a8505d21dba00829c4094c6779d72a88bec1a03bbb83cf028d95ff5f32f5b01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994ac9ba2a7fb8572e971bcac01a5b58934b385a172f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a043ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848a017a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af", ethereumAccessList.getRLPEncoding()); + + // 1 y-parity + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo("0xc5fb1386b60160614a8151dcd4b0ae41325d1cb8") + .setValue("0x1") + .setGas("0x9c40") + .setNonce("0x23") + .setGasPrice("0x5d21dba00") + .setChainId("0x2710") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create(Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0x5430192ae264b3feff967fc08982b9c6f5694023", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ))) + .setSignatures(new SignatureData( + "0x01", + "0x5ac25e47591243af2d6b8e7f54d608e9e0e0aeb5194d34c17852bd7e376f4857", + "0x095a40394f33e95cce9695d5badf4270f4cc8aff0b5395cefc3a0fe213be1f30" + )) + ); + + Assert.assertEquals("0x7801f90109822710238505d21dba00829c4094c5fb1386b60160614a8151dcd4b0ae41325d1cb801b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf859945430192ae264b3feff967fc08982b9c6f5694023f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a05ac25e47591243af2d6b8e7f54d608e9e0e0aeb5194d34c17852bd7e376f4857a0095a40394f33e95cce9695d5badf4270f4cc8aff0b5395cefc3a0fe213be1f30", ethereumAccessList.getRLPEncoding()); + } + @Test public void throwException_NoNonce() { expectedException.expect(RuntimeException.class); From 8ad7f3caa95387f1fd8e3e4e7bc50924a22b7a0f Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 00:23:09 +0900 Subject: [PATCH 56/89] Update signature rlp decoding logic. Cover when given v value was 0. --- .../com/klaytn/caver/wallet/keyring/SignatureData.java | 6 ++++++ .../caver/common/transaction/EthereumAccessListTest.java | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java index 025efacb2..1937183a9 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; @@ -68,6 +70,10 @@ public SignatureData(String v, String r, String s) { * @param s The ECDSA Signature data S */ public SignatureData(byte[] v, byte[] r, byte[] s) { + if (v.length == 0) { + // It handles when given v value was 0(represented "0x80" in rlp encoded). + v = BytesUtils.concat(new byte[]{0x0}, v); + } this.v = Numeric.toHexString(v); this.r = Numeric.toHexString(r); this.s = Numeric.toHexString(s); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 349cfdfa2..490870d57 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -480,8 +480,10 @@ public void getRLPEncodingYParity() { "0x17a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af" )) ); - - Assert.assertEquals("0x7801f901098227101a8505d21dba00829c4094c6779d72a88bec1a03bbb83cf028d95ff5f32f5b01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994ac9ba2a7fb8572e971bcac01a5b58934b385a172f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a043ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848a017a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af", ethereumAccessList.getRLPEncoding()); + String rlpEncoded = ethereumAccessList.getRLPEncoding(); + Assert.assertEquals("0x7801f901098227101a8505d21dba00829c4094c6779d72a88bec1a03bbb83cf028d95ff5f32f5b01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994ac9ba2a7fb8572e971bcac01a5b58934b385a172f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a043ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848a017a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af", rlpEncoded); + EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(rlpEncoded); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); // 1 y-parity ethereumAccessList = caver.transaction.ethereumAccessList.create( @@ -509,7 +511,10 @@ public void getRLPEncodingYParity() { )) ); + rlpEncoded = ethereumAccessList.getRLPEncoding(); Assert.assertEquals("0x7801f90109822710238505d21dba00829c4094c5fb1386b60160614a8151dcd4b0ae41325d1cb801b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf859945430192ae264b3feff967fc08982b9c6f5694023f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a05ac25e47591243af2d6b8e7f54d608e9e0e0aeb5194d34c17852bd7e376f4857a0095a40394f33e95cce9695d5badf4270f4cc8aff0b5395cefc3a0fe213be1f30", ethereumAccessList.getRLPEncoding()); + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(rlpEncoded); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); } @Test From 6179968a008d09e514f642492ecebd6cffa6e736 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 00:25:45 +0900 Subject: [PATCH 57/89] Chore: change test case name --- .../klaytn/caver/common/transaction/EthereumAccessListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 490870d57..46af32536 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -454,7 +454,7 @@ public void getRLPEncodingAndDecoding() { } @Test - public void getRLPEncodingYParity() { + public void getRLPEncodingAndDecodingYParity() { // 0 y-parity EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( TxPropertyBuilder.ethereumAccessList() From 4747d6cdc921c0aa3cbc11777e1f00768fe4961a Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 02:27:04 +0900 Subject: [PATCH 58/89] Handle newly added transaction format when using RPC. * Add additional fields for new ethereum tx type * Add deserializing tests. --- .../caver/methods/response/Transaction.java | 80 +++++++++++++------ .../methods/response/TransactionReceipt.java | 31 ++++++- .../response/TransactionReceiptTest.java | 65 +++++++++++++++ .../methods/response/TransactionTest.java | 64 +++++++++++++++ .../transaction/TransactionHelperTest.java | 7 ++ .../src/test/resources/TransactionSample.json | 32 ++++++++ 6 files changed, 255 insertions(+), 24 deletions(-) create mode 100644 core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java create mode 100644 core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java index ea9b5ea6a..64f026e52 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java @@ -26,6 +26,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.protocol.core.Response; @@ -148,9 +149,19 @@ public static class TransactionData { */ private String value; + /** + * Chain ID. + */ + private String chainID; + + /** + * An access list. + */ + private AccessList accessList; + public TransactionData() {} - public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value) { + public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value, String chainID, AccessList accesslist) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -172,6 +183,8 @@ public TransactionData(String blockHash, String blockNumber, String codeFormat, this.type = type; this.typeInt = typeInt; this.value = value; + this.chainID = chainID; + this.accessList = accesslist; } public String getBlockHash() { @@ -344,6 +357,25 @@ public void setKey(String key) { this.key = key; } + public String getChainID() { + if (chainID == null) { + return ""; + } + return chainID; + } + + public void setChainID(String chainID) { + this.chainID = chainID; + } + + public AccessList getAccessList() { + return accessList; + } + + public void setAccessList(AccessList accessList) { + this.accessList = accessList; + } + /** * Convert TransactionData to Caver's transaction instance. * @param klay The Klay instance to fill gasPrice, chainId and nonce fields when signing a transaction. @@ -352,49 +384,51 @@ public void setKey(String key) { public AbstractTransaction convertToCaverTransaction(Klay klay) { switch (TransactionType.valueOf(this.getType())) { case TxTypeLegacyTransaction: - return LegacyTransaction.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getInput(), this.getValue()); + return LegacyTransaction.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue()); case TxTypeValueTransfer: - return ValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue()); + return ValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue()); case TxTypeFeeDelegatedValueTransfer: - return FeeDelegatedValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue()); + return FeeDelegatedValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue()); case TxTypeFeeDelegatedValueTransferWithRatio: - return FeeDelegatedValueTransferWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue()); + return FeeDelegatedValueTransferWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue()); case TxTypeValueTransferMemo: - return ValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); + return ValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedValueTransferMemo: - return FeeDelegatedValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedValueTransferMemoWithRatio: - return FeeDelegatedValueTransferMemoWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedValueTransferMemoWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); case TxTypeAccountUpdate: - return AccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return AccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeFeeDelegatedAccountUpdate: - return FeeDelegatedAccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return FeeDelegatedAccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeFeeDelegatedAccountUpdateWithRatio: - return FeeDelegatedAccountUpdateWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return FeeDelegatedAccountUpdateWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeSmartContractDeploy: - return SmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return SmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeFeeDelegatedSmartContractDeploy: - return FeeDelegatedSmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return FeeDelegatedSmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeFeeDelegatedSmartContractDeployWithRatio: - return FeeDelegatedSmartContractDeployWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return FeeDelegatedSmartContractDeployWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeSmartContractExecution: - return SmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); + return SmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedSmartContractExecution: - return FeeDelegatedSmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedSmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedSmartContractExecutionWithRatio: - return FeeDelegatedSmartContractExecutionWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedSmartContractExecutionWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); case TxTypeCancel: - return Cancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures()); + return Cancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures()); case TxTypeFeeDelegatedCancel: - return FeeDelegatedCancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures()); + return FeeDelegatedCancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures()); case TxTypeFeeDelegatedCancelWithRatio: - return FeeDelegatedCancelWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio()); + return FeeDelegatedCancelWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio()); case TxTypeChainDataAnchoring: - return ChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getInput()); + return ChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getInput()); case TxTypeFeeDelegatedChainDataAnchoring: - return FeeDelegatedChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getInput()); + return FeeDelegatedChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getInput()); case TxTypeFeeDelegatedChainDataAnchoringWithRatio: - return FeeDelegatedChainDataAnchoringWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getInput()); + return FeeDelegatedChainDataAnchoringWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getInput()); + case TxTypeEthereumAccessList: + return EthereumAccessList.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue(), this.getAccessList()); default: throw new RuntimeException("Invalid transaction type : Cannot create a transaction instance that has Tx type :" + this.getType()); } diff --git a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java index 39f4eb8f9..808e773d9 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.klaytn.caver.crypto.KlaySignatureData; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.protocol.core.Response; import org.web3j.utils.Numeric; @@ -219,10 +220,20 @@ public static class TransactionReceiptData { */ private String value; + /** + * Chain ID. + */ + private String chainID; + + /** + * An access list. + */ + private AccessList accessList; + public TransactionReceiptData() { } - public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value) { + public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value, String chainID, AccessList accessList) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -250,6 +261,8 @@ public TransactionReceiptData(String blockHash, String blockNumber, String codeF this.type = type; this.typeInt = typeInt; this.value = value; + this.chainID = chainID; + this.accessList = accessList; } public String getBlockHash() { @@ -469,6 +482,22 @@ public String getValue() { public void setValue(String value) { this.value = value; } + + public String getChainID() { + return chainID; + } + + public void setChainID(String chainID) { + this.chainID = chainID; + } + + public AccessList getAccessList() { + return accessList; + } + + public void setAccessList(AccessList accessList) { + this.accessList = accessList; + } } public static class SignatureDataListDeserializer extends JsonDeserializer> { diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java new file mode 100644 index 000000000..7812d590c --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java @@ -0,0 +1,65 @@ +package com.klaytn.caver.common.methods.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.methods.response.TransactionReceipt; +import org.junit.Test; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +public class TransactionReceiptTest { + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class ethereumAccessListTest { + @Test + public void deserializeTest() throws IOException { + String ethereumAccessListReceiptJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e\",\n" + + " \"blockNumber\": 91,\n" + + " \"chainID\": \"0x7e3\",\n" + + " \"contractAddress\": null,\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0xf423f\",\n" + + " \"gasPrice\": \"0x5d21dba00\",\n" + + " \"gasUsed\": 25300,\n" + + " \"input\": \"0x\",\n" + + " \"logs\": [],\n" + + " \"logsBloom\": \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"R\": \"0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75\",\n" + + " \"S\": \"0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42\",\n" + + " \"V\": \"0x1\"\n" + + " }\n" + + " ],\n" + + " \"status\": \"0x1\",\n" + + " \"to\": \"0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee\",\n" + + " \"transactionHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"transactionIndex\": 0,\n" + + " \"type\": \"TxTypeEthereumAccessList\",\n" + + " \"typeInt\": 30721,\n" + + " \"value\": \"0x1\"\n" + + "}"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumAccessListReceiptJson); + TransactionReceipt.TransactionReceiptData transactionReceiptData = objectMapper.readValue(reader, TransactionReceipt.TransactionReceiptData.class); + System.out.println(objectToString(transactionReceiptData)); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java new file mode 100644 index 000000000..69268eed5 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java @@ -0,0 +1,64 @@ +package com.klaytn.caver.common.methods.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.methods.response.Transaction; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +@RunWith(Enclosed.class) +public class TransactionTest { + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class ethereumAccessListTest { + @Test + public void deserializeTest() throws IOException { + String ethereumAccessListJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e\",\n" + + " \"blockNumber\": 91,\n" + + " \"chainID\": \"0x7e3\",\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": 999999,\n" + + " \"gasPrice\": 25000000000,\n" + + " \"hash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"input\": \"0x\",\n" + + " \"nonce\": 0,\n" + + " \"senderTxHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"R\": \"0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75\",\n" + + " \"S\": \"0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42\",\n" + + " \"V\": \"0x1\"\n" + + " }\n" + + " ],\n" + + " \"to\": \"0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee\",\n" + + " \"transactionIndex\": 0,\n" + + " \"type\": \"TxTypeEthereumAccessList\",\n" + + " \"typeInt\": 30721,\n" + + " \"value\": 1\n" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumAccessListJson); + Transaction.TransactionData transactionData = objectMapper.readValue(reader, Transaction.TransactionData.class); + System.out.println(objectToString(transactionData)); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java index 59ddb45da..673697d23 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java @@ -83,6 +83,13 @@ public void legacyTransaction() { checkTxObject(txObject, legacyTransaction); } + @Test + public void ethereumAccessList() { + Transaction.TransactionData txObject = sampleTxData.get("ethereumAccessList"); + EthereumAccessList ethereumAccessList = (EthereumAccessList)txObject.convertToCaverTransaction(caver.rpc.klay); + checkTxObject(txObject, ethereumAccessList); + } + @Test public void valueTransfer() { Transaction.TransactionData txObject = sampleTxData.get("valueTransfer"); diff --git a/core/src/test/resources/TransactionSample.json b/core/src/test/resources/TransactionSample.json index c16032c5f..1ab5bfe70 100644 --- a/core/src/test/resources/TransactionSample.json +++ b/core/src/test/resources/TransactionSample.json @@ -616,5 +616,37 @@ "transactionIndex":"0x0", "type":"TxTypeFeeDelegatedChainDataAnchoringWithRatio", "typeInt":74 + }, + "ethereumAccessList":{ + "accessList": [ + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f" + ] + } + ], + "blockHash": "0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e", + "blockNumber": "0x5b", + "chainID": "0x7e3", + "from": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "gas": "0xf423f", + "gasPrice": "0x5d21dba00", + "hash": "0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71", + "input": "0x", + "nonce": "0x0", + "senderTxHash": "0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71", + "signatures": [ + { + "R": "0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75", + "S": "0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42", + "V": "0x1" + } + ], + "to": "0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee", + "transactionIndex": "0x0", + "type": "TxTypeEthereumAccessList", + "typeInt": 30721, + "value": "0x1" } } \ No newline at end of file From 18b540999145c6150c4638a8cb9531d607425fe7 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 08:50:45 +0900 Subject: [PATCH 59/89] Use plain name Builder --- .../caver/transaction/type/EthereumAccessList.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 6f27c465c..38cd61132 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -74,27 +74,27 @@ public Builder() { super(TransactionType.TxTypeEthereumAccessList.toString()); } - public EthereumAccessList.Builder setValue(String value) { + public Builder setValue(String value) { this.value = value; return this; } - public EthereumAccessList.Builder setValue(BigInteger value) { + public Builder setValue(BigInteger value) { setValue(Numeric.toHexStringWithPrefix(value)); return this; } - public EthereumAccessList.Builder setInput(String input) { + public Builder setInput(String input) { this.input = input; return this; } - public EthereumAccessList.Builder setTo(String to) { + public Builder setTo(String to) { this.to = to; return this; } - public EthereumAccessList.Builder setAccessList(AccessList accessList) { + public Builder setAccessList(AccessList accessList) { this.accessList = accessList; return this; } From dcc64baba4ab49c57000d729b3e4ea600c67dc3d Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 09:05:17 +0900 Subject: [PATCH 60/89] Apply suggestions from code review Co-authored-by: Jamie <32922423+jimni1222@users.noreply.github.com> --- .../com/klaytn/caver/transaction/type/TransactionType.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 17657c4eb..07cd70e57 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -70,7 +70,8 @@ public int getType() { public static boolean isEthereumTransaction(int type) { if (type == TxTypeLegacyTransaction.type || type == TxTypeEthereumAccessList.type) { return true; - } else return false; + } + return false; } /** @@ -85,7 +86,8 @@ public static boolean isEthereumTransaction(String type) { Objects.equals(type, TxTypeEthereumAccessList.toString()) ) { return true; - } else return false; + } + return false; } From fe5854f90041767810c0c98c808f843dbff295eb Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 10:03:50 +0900 Subject: [PATCH 61/89] Update comments about v --- .../java/com/klaytn/caver/wallet/keyring/SignatureData.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java index 1937183a9..43dfef577 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java @@ -169,7 +169,9 @@ public RlpList toRlpList() { RlpString rlpV; byte[] trimmedV = Bytes.trimLeadingZeroes(v); if (trimmedV[0] == 0) { - // When given v value is "0x0", it must be treated as integer 0 by encoding rule. + // If v value is "0x0", the shape of trimmedV is [0x0] (have 1 element). + // If we create rlpV by using RlpString.create(byte[]{0x0}), it is encoded as `0x00` but + // v value is integer by its spec and integer 0 must be encoded as 0x80. rlpV = RlpString.create(BigInteger.valueOf(0)); } else { rlpV = RlpString.create(trimmedV); From db60251f0b85c6a04ffe1f9a6b17e5d71083b778 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 12:16:30 +0900 Subject: [PATCH 62/89] Add missing comments to overrided methods --- .../transaction/type/EthereumAccessList.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 38cd61132..df0a81518 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -353,6 +353,10 @@ public void appendSignatures(List signatureData) { super.appendSignatures(signatureData); } + /** + * Returns a hash string of transaction + * @return String + */ @Override public String getTransactionHash() { // TxHashRLP = 0x01 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) @@ -362,12 +366,28 @@ public String getTransactionHash() { return Hash.sha3(Numeric.toHexString(detachedType)); } + /** + * Signs to the transaction with a single private key. + * It sets Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyString The private key string. + * @return AbstractTransaction + * @throws IOException + */ @Override public AbstractTransaction sign(String keyString) throws IOException { AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); return this.sign(keyring, TransactionHasher::getHashForSignature); } + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * @param keyring The Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ @Override public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { @@ -392,22 +412,54 @@ public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); return this.sign(keyring, signer); } + /** + * Signs to the transaction with a private key in the Keyring instance. + * It sets signer to TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ @Override public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOException { return this.sign(keyring, index, TransactionHasher::getHashForSignature); } + /** + * Signs to the transaction with a private key in the Keyring instance. + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ @Override public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { From 22ae9c4e2118d00d2f378f047f921de7308044b4 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 13:31:15 +0900 Subject: [PATCH 63/89] Move checking EthereumTxType logic to TransactionHelper --- .../transaction/AbstractTransaction.java | 8 ++--- .../caver/transaction/TransactionHelper.java | 33 +++++++++++++++++++ .../transaction/type/EthereumAccessList.java | 5 +-- .../transaction/type/TransactionType.java | 30 ----------------- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index c4342ede9..459be6d2e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -267,7 +267,7 @@ public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { - if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } @@ -311,7 +311,7 @@ public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOExc * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { - if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } @@ -513,7 +513,7 @@ public void validateOptionalValues(boolean checkChainID) { * @return List<String> */ public List refineSignature(List signatureDataList) { - boolean isEthereumTransaction = TransactionType.isEthereumTransaction(this.getType()); + boolean isEthereumTransaction = TransactionHelper.isEthereumTransaction(this.getType()); List refinedList = SignatureData.refineSignature(signatureDataList); @@ -647,7 +647,7 @@ public void setType(String type) { public void setFrom(String from) { //"From" field in EthereumTransaction allows null - if(TransactionType.isEthereumTransaction(this.getType())) { + if(TransactionHelper.isEthereumTransaction(this.getType())) { if(from == null || from.isEmpty() || from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS)) from = Utils.DEFAULT_ZERO_ADDRESS; } else { if(from == null) { diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java index 142e6db5d..811967f73 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java @@ -18,9 +18,13 @@ import com.klaytn.caver.methods.response.Transaction; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.type.TransactionType; import java.io.IOException; import java.util.List; +import java.util.Objects; + +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeEthereumAccessList; /** * This class is a helper class provides methods that handles Transaction object comfortably. @@ -84,4 +88,33 @@ public static List recoverFeePayerPublicKeys(String rawTx) { AbstractFeeDelegatedTransaction tx = (AbstractFeeDelegatedTransaction)TransactionDecoder.decode(rawTx); return tx.recoverFeePayerPublicKeys(); } + + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type integer. + * @return + */ + public static boolean isEthereumTransaction(int type) { + if (type == TransactionType.TxTypeLegacyTransaction.getType() || type == TxTypeEthereumAccessList.getType()) { + return true; + } + return false; + } + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type string. + * @return + */ + public static boolean isEthereumTransaction(String type) { + if ( + Objects.equals(type, TransactionType.TxTypeLegacyTransaction.toString()) || + Objects.equals(type, TransactionType.TxTypeEthereumAccessList.toString()) + ) { + return true; + } + return false; + } + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index df0a81518..445495b80 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -20,6 +20,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; @@ -390,7 +391,7 @@ public AbstractTransaction sign(String keyString) throws IOException { */ @Override public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { - if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } @@ -462,7 +463,7 @@ public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOExc */ @Override public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { - if(TransactionType.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 07cd70e57..68f2770a8 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -61,34 +61,4 @@ public int getType() { return type; } - /** - * Returns true if the tx type is EthereumTransaction. - * - * @param type Transaction type integer. - * @return - */ - public static boolean isEthereumTransaction(int type) { - if (type == TxTypeLegacyTransaction.type || type == TxTypeEthereumAccessList.type) { - return true; - } - return false; - } - - /** - * Returns true if the tx type is EthereumTransaction. - * - * @param type Transaction type string. - * @return - */ - public static boolean isEthereumTransaction(String type) { - if ( - Objects.equals(type, TxTypeLegacyTransaction.toString()) || - Objects.equals(type, TxTypeEthereumAccessList.toString()) - ) { - return true; - } - return false; - } - - } From 215b2e0c4b27b3f5953101ed647f1eef7381ec04 Mon Sep 17 00:00:00 2001 From: Tony Lee <52311277+iv0rish@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:33:43 +0900 Subject: [PATCH 64/89] Update machine image to ubuntu 20.04 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42763c9c3..6037407a2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ defaults: &defaults machine_ubuntu: &machine_ubuntu working_directory: ~/circleci-caver-java-major machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202010-01 android_machine_ubuntu: &android_machine_ubuntu working_directory: ~/circleci-caver-java-android From 22544711fe4776cb45620bd8b11ae4d667c7c419 Mon Sep 17 00:00:00 2001 From: Tony Lee <52311277+iv0rish@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:34:15 +0900 Subject: [PATCH 65/89] Delete sotre_artifacts step from android test --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6037407a2..475588245 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -195,9 +195,6 @@ jobs: command: ./gradlew connectedDebugAndroidTest --debug --stacktrace - store_test_results: path: android_instrumented_test/build/outputs/androidTest-results/connected - - store_artifacts: - path: ~/circleci-caver-java-android/ - destination: adb.log build_test_android: <<: *android_machine_ubuntu From 0328b83529e8e92867f6b96a1187f906b648dfee Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 14:04:53 +0900 Subject: [PATCH 66/89] Accepted v value as 0 or 1 at SignatureData#getRecoveryId() --- .../java/com/klaytn/caver/wallet/keyring/SignatureData.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java index 9b5186eb6..0de724183 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java @@ -175,6 +175,12 @@ public RlpList toRlpList() { public int getRecoverId() { int v = Numeric.toBigInt(this.getV()).intValue() & 0xFFFF; + // The v value of EthereumAccessList or EthereumDynamicFee transaction has 0 or 1(parity value of y value in Signature). + // It should be accepted 0 or 1 value + if (v == 0 || v == 1) { + return v; + } + if (v < 27) { throw new RuntimeException("v byte out of range: " + v); } From d5c0c6242d97d2403261ccd9f46477501a784ba8 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 14:07:03 +0900 Subject: [PATCH 67/89] add test. --- .../klaytn/caver/common/utils/UtilsTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java b/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java index 9d3e9acff..738f06b6d 100644 --- a/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java +++ b/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java @@ -1161,6 +1161,30 @@ public void alreadyPrefix() throws SignatureException { checkAddress(keyring.getAddress(), actualAddress); } + + @Test + public void withMessageAndSignatureIfVis0() throws SignatureException { + String expectedSignedMessage = "0xc69018da9396c4b87947e0784625af7475caf46e2af9cf57a44673ff0f625258642d8993751ae67271bcc131aa065adccf9f16fc4953f9c48f4a80d675c09ae800"; + String expectedAddress = "0xb6a1f97502431e6f8d701f9e192c3cc43c07351a"; + String message = "Klaytn Test"; + + SignatureData signatureData = caver.utils.decodeSignature(expectedSignedMessage); + String actualAddress = caver.utils.recover(message, signatureData); + + checkAddress(expectedAddress, actualAddress); + } + + @Test + public void withMessageAndSignatureIfVis1() throws SignatureException { + String expectedSignedMessage = "0x90dda6bb189ccb6cbda754c3696b91d28ca2486d7f2905ac90e1a831a2bb65cd4c55a54d6365ea5cb0f33330ed82b687d7b808137a0a7cdc5b620bb81d69d62701"; + String expectedAddress = "0xa05b2b058dbc5926969cb7f33cff6d8747472022"; + String message = "Some data"; + + SignatureData signatureData = caver.utils.decodeSignature(expectedSignedMessage); + String actualAddress = caver.utils.recover(message, signatureData); + + checkAddress(expectedAddress, actualAddress); + } } public static class decodeSignatureTest { @@ -1237,6 +1261,37 @@ public void recoverWithSampleData() throws SignatureException { String actual = caver.utils.recoverPublicKey(message, signatureData); assertEquals(expected, actual); } + + @Test + public void recoverWithSampleDataIfVis0() throws SignatureException { + String expected = "0xb5df4d5e6b4ee7a136460b911a69030fdd42c18ed067bcc2e25eda1b851314fad994c5fe946aad01ca2e348d4ff3094960661a8bc095f358538af54aeea48ff3"; + + String message = "Some Message"; + SignatureData signatureData = new SignatureData( + "0x00", + "0x8213e560e7bbe1f2e28fd69cbbb41c9108b84c98cd7c2c88d3c8e3549fd6ab10", + "0x3ca40c9e20c1525348d734a6724db152b9244bff6e0ff0c2b811d61d8f874f00" + ); + + String actual = caver.utils.recoverPublicKey(message, signatureData); + assertEquals(expected, actual); + } + + @Test + public void recoverWithSampleDataIfVis1() throws SignatureException { + String expected = "0xd907f61c1270ed9601b20a53d55244fea002afca955e1a47a309c507ced26e8721d32f419f789d6216ca35bd47aa7adb2432cfc6d52dd74b552a1b749181c0ac"; + String address = caver.utils.publicKeyToAddress(expected); + + String message = "Some data"; + SignatureData signatureData = new SignatureData( + "0x01", + "0x90dda6bb189ccb6cbda754c3696b91d28ca2486d7f2905ac90e1a831a2bb65cd", + "0x4c55a54d6365ea5cb0f33330ed82b687d7b808137a0a7cdc5b620bb81d69d627" + ); + + String actual = caver.utils.recoverPublicKey(message, signatureData); + assertEquals(expected, actual); + } } public static class publicKeyToAddress { From 97d7670d8d77ee71d07b43ba5966d6d3b74b0094 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:08:57 +0900 Subject: [PATCH 68/89] Check parity when set signatures to EthereumAccessList tx --- .../transaction/type/EthereumAccessList.java | 14 +++++ .../transaction/EthereumAccessListTest.java | 57 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 445495b80..558a39d9b 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -333,6 +333,12 @@ public void appendSignatures(SignatureData signatureData) { throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); } + if (!Utils.isEmptySig(signatureData)) { + int v = Integer.decode(signatureData.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + }; super.appendSignatures(signatureData); } @@ -351,6 +357,14 @@ public void appendSignatures(List signatureData) { throw new RuntimeException("Signatures are too long " + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); } + SignatureData signature = signatureData.get(0); + if (!Utils.isEmptySig(signature)) { + int v = Integer.decode(signature.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + }; + super.appendSignatures(signatureData); } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 46af32536..72fd9a395 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -530,7 +530,7 @@ public void throwException_NoNonce() { String value = "0xa"; SignatureData signatureData = new SignatureData( - Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0x1"), Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") ); @@ -566,7 +566,7 @@ public void throwException_NoGasPrice() { String value = "0xa"; SignatureData signatureData = new SignatureData( - Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0x0"), Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") ); @@ -602,7 +602,7 @@ public void throwException_NoChainId() { String value = "0xa"; SignatureData signatureData = new SignatureData( - Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0x0"), Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") ); @@ -994,7 +994,7 @@ public void throwException_existSignature() { .setChainId(chainID) .setAccessList(accessList) .setSignatures(new SignatureData( - Numeric.hexStringToByteArray("0x0fea"), + Numeric.hexStringToByteArray("0x1"), Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") )) @@ -1080,7 +1080,7 @@ public static class appendSignaturesTest { ); static SignatureData signatureData = new SignatureData( - Numeric.hexStringToByteArray("0x0fea"), + Numeric.hexStringToByteArray("0x1"), Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") ); @@ -1103,6 +1103,53 @@ public void appendSignature() { assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); } + @Test + public void throwException_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + ); + + ethereumAccessList.appendSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )); + } + + + @Test + public void throwException_setSignature_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + } + + @Test public void appendSignatureWithEmptySig() { SignatureData emptySignature = SignatureData.getEmptySignature(); From 1d36f5ab9612007ed82203ca414d426440a84a39 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:33:59 +0900 Subject: [PATCH 69/89] Remove gasPrice from AbstractTransaction * Add setter and getter for gasPrice for each transaction which have gasPrice field. * Override combineSignedRawTransaction * Hard to make common usage because now we cannot compare gasPrice field by using Abstract class. * Override fillTransaction * For common fields, use super.fillTransaction. * Override validateOptionalValues * For common fields, use super.validateOptionalValues. --- .../AbstractFeeDelegatedTransaction.java | 47 +----- ...tractFeeDelegatedWithRatioTransaction.java | 5 +- .../transaction/AbstractTransaction.java | 94 +----------- .../caver/transaction/type/AccountUpdate.java | 122 +++++++++++++++- .../klaytn/caver/transaction/type/Cancel.java | 133 ++++++++++++++++- .../transaction/type/ChainDataAnchoring.java | 132 ++++++++++++++++- .../transaction/type/EthereumAccessList.java | 120 ++++++++++++++- .../type/FeeDelegatedAccountUpdate.java | 124 +++++++++++++++- .../FeeDelegatedAccountUpdateWithRatio.java | 132 ++++++++++++++++- .../transaction/type/FeeDelegatedCancel.java | 132 ++++++++++++++++- .../type/FeeDelegatedCancelWithRatio.java | 138 +++++++++++++++++- .../type/FeeDelegatedChainDataAnchoring.java | 129 +++++++++++++++- ...eDelegatedChainDataAnchoringWithRatio.java | 130 ++++++++++++++++- .../type/FeeDelegatedSmartContractDeploy.java | 130 ++++++++++++++++- ...DelegatedSmartContractDeployWithRatio.java | 131 ++++++++++++++++- .../FeeDelegatedSmartContractExecution.java | 128 +++++++++++++++- ...egatedSmartContractExecutionWithRatio.java | 130 ++++++++++++++++- .../type/FeeDelegatedValueTransfer.java | 129 +++++++++++++++- .../type/FeeDelegatedValueTransferMemo.java | 131 ++++++++++++++++- ...eeDelegatedValueTransferMemoWithRatio.java | 129 +++++++++++++++- .../FeeDelegatedValueTransferWithRatio.java | 130 ++++++++++++++++- .../transaction/type/LegacyTransaction.java | 116 ++++++++++++++- .../transaction/type/SmartContractDeploy.java | 115 ++++++++++++++- .../type/SmartContractExecution.java | 115 ++++++++++++++- .../caver/transaction/type/ValueTransfer.java | 115 ++++++++++++++- .../transaction/type/ValueTransferMemo.java | 115 ++++++++++++++- .../transaction/TransactionHelperTest.java | 1 - 27 files changed, 2888 insertions(+), 165 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java index 7028e55bb..79d060309 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java @@ -102,14 +102,13 @@ public AbstractFeeDelegatedTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. */ - public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures); + public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures); setFeePayer(feePayer); setFeePayerSignatures(feePayerSignatures); } @@ -238,48 +237,6 @@ public void appendFeePayerSignatures(List signatureData) { setFeePayerSignatures(signatureData); } - /** - * Combines signatures and feePayerSignatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. - * When combining the signatures into a transaction instance, - * an error is thrown if the decoded transaction contains different value except signatures. - * @param rlpEncoded A List of RLP-encoded transaction strings. - * @return String - */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; - - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. - if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; - - for(String encodedStr : rlpEncoded) { - AbstractFeeDelegatedTransaction txObj = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - this.setFeePayer(txObj.getFeePayer()); - fillVariable = false; - } - } - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); - } - - return this.getRLPEncoding(); - } - /** * Returns a RLP-encoded transaction string for making fee payer's signature. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java index 79b6b0ff1..6a2aea843 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java @@ -74,15 +74,14 @@ public AbstractFeeDelegatedWithRatioTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. * @param feeRatio A fee ratio of the fee payer. */ - public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures); + public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures, feePayer, feePayerSignatures); setFeeRatio(feeRatio); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index 459be6d2e..ed9c9cd63 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -71,11 +71,6 @@ abstract public class AbstractTransaction { */ private String gas; - /** - * A unit price of gas in peb the sender will pay for a transaction fee. - */ - private String gasPrice = "0x"; - /** * Network ID */ @@ -96,7 +91,6 @@ public static class Builder { private String from; private String nonce = "0x"; - private String gasPrice = "0x"; private String chainId = "0x"; private Klay klaytnCall = null; private List signatures = new ArrayList<>(); @@ -130,16 +124,6 @@ public B setGas(BigInteger gas) { return (B) this; } - public B setGasPrice(String gasPrice) { - this.gasPrice = gasPrice; - return (B) this; - } - - public B setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - return (B) this; - } - public B setChainId(String chainId) { this.chainId = chainId; return (B) this; @@ -180,7 +164,6 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { builder.from, builder.nonce, builder.gas, - builder.gasPrice, builder.chainId, builder.signatures ); @@ -193,16 +176,14 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list */ - public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { + public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures) { setKlaytnCall(klaytnCall); setType(type); setFrom(from); setNonce(nonce); - setGasPrice(gasPrice); setGas(gas); setChainId(chainId); setSignatures(signatures); @@ -360,33 +341,8 @@ public void appendSignatures(List signatureData) { * @param rlpEncoded A List of RLP-encoded transaction strings. * @return String */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; + public abstract String combineSignedRawTransactions(List rlpEncoded); - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; - - for(String encodedStr : rlpEncoded) { - AbstractTransaction txObj = TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - fillVariable = false; - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - } - - return this.getRLPEncoding(); - } /** * Returns a RawTransaction(RLP-encoded transaction string) @@ -445,15 +401,10 @@ public void fillTransaction() throws IOException{ if(this.chainId.equals("0x")) { this.chainId = klaytnCall.getChainID().send().getResult(); } - - if(this.gasPrice.equals("0x")) { - this.gasPrice = klaytnCall.getGasPrice().send().getResult(); - } - } - if(this.nonce.equals("0x") || this.chainId.equals("0x") || this.gasPrice.equals("0x")) { - throw new RuntimeException("Cannot fill transaction data.(nonce, chainId, gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + if(this.nonce.equals("0x") || this.chainId.equals("0x")) { + throw new RuntimeException("Cannot fill transaction data.(nonce, chainId). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); } } @@ -468,7 +419,6 @@ public boolean compareTxField(AbstractTransaction txObj, boolean checkSig) { if(!this.getFrom().toLowerCase().equals(txObj.getFrom().toLowerCase())) return false; if(!Numeric.toBigInt(this.getNonce()).equals(Numeric.toBigInt(txObj.getNonce()))) return false; if(!Numeric.toBigInt(this.getGas()).equals(Numeric.toBigInt(txObj.getGas()))) return false; - if(!Numeric.toBigInt(this.getGasPrice()).equals(Numeric.toBigInt(txObj.getGasPrice()))) return false; if(checkSig) { List dataList = this.getSignatures(); @@ -493,10 +443,6 @@ public void validateOptionalValues(boolean checkChainID) { throw new RuntimeException("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); } - if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { - throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); - } - if(checkChainID) { if(this.getChainId() == null || this.getChainId().isEmpty() || this.getChainId().equals("0x")) { throw new RuntimeException("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); @@ -612,14 +558,6 @@ public String getGas() { return gas; } - /** - * Getter function for gas price - * @return String - */ - public String getGasPrice() { - return gasPrice; - } - /** * Getter function for chain id * @return String @@ -710,30 +648,6 @@ public void setNonce(BigInteger nonce) { setNonce(Numeric.toHexStringWithPrefix(nonce)); } - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(String gasPrice) { - if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { - gasPrice = "0x"; - } - - if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { - throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); - } - - this.gasPrice = gasPrice; - } - - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - } - /** * Setter function for chain id. * @param chainId A network id. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 78359635d..0c3387a4e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -19,11 +19,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -40,11 +43,17 @@ public class AccountUpdate extends AbstractTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * AccountUpdate Builder class */ public static class Builder extends AbstractTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeAccountUpdate.toString()); @@ -55,6 +64,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public AccountUpdate build() { return new AccountUpdate(this); } @@ -92,6 +111,7 @@ public static AccountUpdate create(Klay klaytnCall, String from, String nonce, S public AccountUpdate(AccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -112,11 +132,43 @@ public AccountUpdate(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -241,10 +293,78 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getAccount().getRLPEncodingAccountKey().equals(txObj.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + AccountUpdate txObj = (AccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index daf278680..ee06f6f0b 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -18,11 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,15 +36,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypecancel to see more detail. */ public class Cancel extends AbstractTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * Cancel Builder class */ public static class Builder extends AbstractTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public Cancel build() { return new Cancel(this); } @@ -77,6 +96,7 @@ public static Cancel create(Klay klaytnCall, String from, String nonce, String g */ public Cancel(Cancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -90,9 +110,51 @@ public Cancel(Cancel.Builder builder) { * @param signatures A Signature list */ public Cancel(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { - super(klaytnCall, TransactionType.TxTypeCancel.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeCancel.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setGasPrice(gasPrice); } + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded Cancel string. * @param rlpEncoded RLP-encoded Cancel string @@ -202,7 +264,76 @@ public String getCommonRLPEncodingForSignature() { public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!super.compareTxField(obj, checkSig)) return false; if(!(obj instanceof Cancel)) return false; + Cancel txObj = (Cancel) obj; + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + Cancel txObj = (Cancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index ee79992f3..169f99b53 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -39,11 +41,17 @@ public class ChainDataAnchoring extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ChainDataAnchoring Builder class */ public static class Builder extends AbstractTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeChainDataAnchoring.toString()); @@ -54,6 +62,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ChainDataAnchoring build() { return new ChainDataAnchoring(this); } @@ -91,6 +109,7 @@ public static ChainDataAnchoring create(Klay klaytnCall, String from, String non public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -105,10 +124,52 @@ public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { * @param input Data of the service chain. */ public ChainDataAnchoring(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String input) { - super(klaytnCall, TransactionType.TxTypeChainDataAnchoring.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeChainDataAnchoring.toString(), + from, + nonce, + gas, + chainId, + signatures + ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded ChainDataAnchoring string. * @param rlpEncoded RLP-encoded ChainDataAnchoring string @@ -226,10 +287,79 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { ChainDataAnchoring txObj = (ChainDataAnchoring)obj; if(!this.getInput().equals(txObj.getInput())) return false; + if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ChainDataAnchoring txObj = (ChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 558a39d9b..5cf0e8103 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -19,6 +19,7 @@ import com.klaytn.caver.account.AccountKeyRoleBased; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.utils.AccessList; @@ -62,6 +63,11 @@ public class EthereumAccessList extends AbstractTransaction { */ AccessList accessList; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * EthereumAccessList Builder class */ @@ -70,6 +76,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + EthereumAccessList txObj = (EthereumAccessList) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } /** * Decodes a RLP-encoded EthereumAccessList string. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 55d9e6528..5627b83dd 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -19,12 +19,16 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -41,11 +45,17 @@ public class FeeDelegatedAccountUpdate extends AbstractFeeDelegatedTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdate Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdate.toString()); @@ -56,6 +66,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdate build() { return new FeeDelegatedAccountUpdate(this); } @@ -95,6 +115,7 @@ public static FeeDelegatedAccountUpdate create(Klay klaytnCall, String from, Str public FeeDelegatedAccountUpdate(FeeDelegatedAccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -117,13 +138,45 @@ public FeeDelegatedAccountUpdate(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -293,10 +346,79 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedAccountUpdate txObj = (FeeDelegatedAccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 8a76e103f..994588369 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -18,13 +18,17 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -41,11 +45,17 @@ public class FeeDelegatedAccountUpdateWithRatio extends AbstractFeeDelegatedWith */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdateWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdateWithRatio.toString()); @@ -56,6 +66,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdateWithRatio build() { return new FeeDelegatedAccountUpdateWithRatio(this); } @@ -96,6 +116,7 @@ public static FeeDelegatedAccountUpdateWithRatio create(Klay klaytnCall, String public FeeDelegatedAccountUpdateWithRatio(Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -119,7 +140,6 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -127,8 +147,42 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n feeRatio ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedAccountUpdateWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedAccountUpdateWithRatio string. @@ -300,10 +354,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedAccountUpdateWithRatio txObj = (FeeDelegatedAccountUpdateWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index acec35cb8..e06cb5984 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -19,12 +19,15 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,15 +38,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedcancel to see more detail. */ public class FeeDelegatedCancel extends AbstractFeeDelegatedTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancel Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancel build() { return new FeeDelegatedCancel(this); } @@ -81,6 +100,7 @@ public static FeeDelegatedCancel create(Klay klaytnCall, String from, String non */ public FeeDelegatedCancel(FeeDelegatedCancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -102,12 +122,44 @@ public FeeDelegatedCancel(Klay klaytnCall, String from, String nonce, String gas from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -261,7 +313,85 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancel)) return false; + FeeDelegatedCancel feeDelegatedCancel = (FeeDelegatedCancel) txObj; + if (!this.getGasPrice().equals(feeDelegatedCancel.getGasPrice())) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedCancel txObj = (FeeDelegatedCancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index c85c1bbf7..deb3590ed 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -17,13 +17,17 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,15 +38,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedcancelwithratio to see more detail. */ public class FeeDelegatedCancelWithRatio extends AbstractFeeDelegatedWithRatioTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancelWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancelWithRatio build() { return new FeeDelegatedCancelWithRatio(this); } @@ -81,6 +101,7 @@ public static FeeDelegatedCancelWithRatio create(Klay klaytnCall, String from, S */ public FeeDelegatedCancelWithRatio(Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -97,7 +118,51 @@ public FeeDelegatedCancelWithRatio(Builder builder) { * @param feeRatio A fee ratio of the fee payer. */ public FeeDelegatedCancelWithRatio(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures, feeRatio); + super( + klaytnCall, + TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), + from, + nonce, + gas, + chainId, + signatures, + feePayer, + feePayerSignatures, + feeRatio + ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -257,7 +322,78 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancelWithRatio)) return false; + FeeDelegatedCancelWithRatio feeDelegatedCancelWithRatio = (FeeDelegatedCancelWithRatio) txObj; + if (!this.getGasPrice().equals(feeDelegatedCancelWithRatio.getGasPrice())) return false; return true; } + + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedCancelWithRatio txObj = (FeeDelegatedCancelWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index ee7436d53..fab6dc822 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -42,11 +44,17 @@ public class FeeDelegatedChainDataAnchoring extends AbstractFeeDelegatedTransact */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoring Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoring.toString()); @@ -57,6 +65,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoring build() { return new FeeDelegatedChainDataAnchoring(this); } @@ -96,6 +114,7 @@ public static FeeDelegatedChainDataAnchoring create(Klay klaytnCall, String from public FeeDelegatedChainDataAnchoring(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,13 +137,45 @@ public FeeDelegatedChainDataAnchoring(Klay klaytnCall, String from, String nonce from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -286,10 +337,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che FeeDelegatedChainDataAnchoring feeDelegatedChainDataAnchoring = (FeeDelegatedChainDataAnchoring)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoring.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoring.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedChainDataAnchoring txObj = (FeeDelegatedChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index a5dba7c2e..626e86545 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -40,11 +43,17 @@ public class FeeDelegatedChainDataAnchoringWithRatio extends AbstractFeeDelegate */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoringWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoringWithRatio.toString()); @@ -55,6 +64,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoringWithRatio build() { return new FeeDelegatedChainDataAnchoringWithRatio(this); } @@ -95,6 +114,7 @@ public static FeeDelegatedChainDataAnchoringWithRatio create(Klay klaytnCall, St public FeeDelegatedChainDataAnchoringWithRatio(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,7 +138,6 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -126,6 +145,39 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str feeRatio ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -290,10 +342,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo FeeDelegatedChainDataAnchoringWithRatio feeDelegatedChainDataAnchoringWithRatio = (FeeDelegatedChainDataAnchoringWithRatio)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoringWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoringWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedChainDataAnchoringWithRatio txObj = (FeeDelegatedChainDataAnchoringWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index f7d073991..379a8af6a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -18,6 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +38,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractdeploy to see more detail. */ public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -64,6 +65,11 @@ public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransac */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeploy Builder class */ @@ -73,6 +79,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractDeploy txObj = (FeeDelegatedSmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index ad7ff4d53..def36b72e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -64,6 +67,11 @@ public class FeeDelegatedSmartContractDeployWithRatio extends AbstractFeeDelegat */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeployWithRatio Builder class */ @@ -73,6 +81,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String input; boolean humanReadable = false; String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractDeployWithRatio.toString()); @@ -113,6 +122,16 @@ public Builder setCodeFormat(BigInteger codeFormat) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractDeployWithRatio build() { return new FeeDelegatedSmartContractDeployWithRatio(this); } @@ -161,6 +180,7 @@ public FeeDelegatedSmartContractDeployWithRatio(Builder builder) { setInput(builder.input); setHumanReadable(builder.humanReadable); setCodeFormat(builder.codeFormat); + setGasPrice(builder.gasPrice); } /** @@ -188,7 +208,6 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -200,8 +219,42 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St setInput(input); setHumanReadable(humanReadable); setCodeFormat(codeFormat); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. @@ -391,10 +444,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getInput().equals(feeDelegatedSmartContractDeployWithRatio.getInput())) return false; if(this.getHumanReadable() != feeDelegatedSmartContractDeployWithRatio.getHumanReadable()) return false; if(!this.getCodeFormat().equals(feeDelegatedSmartContractDeployWithRatio.getCodeFormat())) return false; + if(!this.getGasPrice().equals(feeDelegatedSmartContractDeployWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractDeployWithRatio txObj = (FeeDelegatedSmartContractDeployWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index 594be3894..2b2613cac 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +53,11 @@ public class FeeDelegatedSmartContractExecution extends AbstractFeeDelegatedTran */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecution Builder class */ @@ -58,6 +65,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractExecution txObj = (FeeDelegatedSmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 43e8f3a78..5900a3785 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -46,6 +49,11 @@ public class FeeDelegatedSmartContractExecutionWithRatio extends AbstractFeeDele */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecutionWithRatio Builder class */ @@ -53,6 +61,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value = "0x00"; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractExecutionWithRatio.toString()); @@ -78,6 +87,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractExecutionWithRatio build() { return new FeeDelegatedSmartContractExecutionWithRatio(this); } @@ -122,6 +141,7 @@ public FeeDelegatedSmartContractExecutionWithRatio(Builder builder) { setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -147,7 +167,6 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -157,8 +176,42 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } + /** * Decodes a RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. @@ -335,10 +388,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedSmartContractExecutionWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedSmartContractExecutionWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedSmartContractExecutionWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractExecutionWithRatio txObj = (FeeDelegatedSmartContractExecutionWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index fd3b40a1b..cb30ed217 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.crypto.KlaySignatureData; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -47,12 +49,18 @@ public class FeeDelegatedValueTransfer extends AbstractFeeDelegatedTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransfer Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransfer.toString()); @@ -73,6 +81,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransfer build() { return new FeeDelegatedValueTransfer(this); } @@ -114,6 +132,7 @@ public FeeDelegatedValueTransfer(Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -137,7 +156,6 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -145,6 +163,39 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -315,10 +366,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransfer.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransfer.getValue()))) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransfer.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransfer txObj = (FeeDelegatedValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 445e354fa..82b6a78e3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +38,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfermemo to see more detail. */ public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -52,6 +53,11 @@ public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransacti */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -59,6 +65,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferMemo txObj = (FeeDelegatedValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index fa2388e52..663a9a929 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +54,11 @@ public class FeeDelegatedValueTransferMemoWithRatio extends AbstractFeeDelegated */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -58,6 +66,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferMemoWithRatio.toString()); @@ -82,6 +91,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferMemoWithRatio build() { return new FeeDelegatedValueTransferMemoWithRatio(this); } @@ -126,6 +145,7 @@ public FeeDelegatedValueTransferMemoWithRatio(FeeDelegatedValueTransferMemoWithR setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -151,7 +171,6 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -161,6 +180,39 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -341,10 +393,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferMemoWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedValueTransferMemoWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransferMemoWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferMemoWithRatio txObj = (FeeDelegatedValueTransferMemoWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index e3b646712..01383e31f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +39,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransferwithratio to see more detail. */ public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWithRatioTransaction { - /** * The account address that will receive the transferred value. */ @@ -47,12 +49,18 @@ public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWith */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferWithRatio.toString()); @@ -73,6 +81,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferWithRatio build() { return new FeeDelegatedValueTransferWithRatio(this); } @@ -115,6 +133,7 @@ public FeeDelegatedValueTransferWithRatio(FeeDelegatedValueTransferWithRatio.Bui super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -139,7 +158,6 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -148,6 +166,39 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -322,10 +373,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getValue()))) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransferWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferWithRatio txObj = (FeeDelegatedValueTransferWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index 025c8a641..03a3583f0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -18,11 +18,13 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -43,6 +45,11 @@ public class LegacyTransaction extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * LegacyTransaction Builder class */ @@ -50,6 +57,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + LegacyTransaction txObj = (LegacyTransaction) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index 925619024..8dbec94ae 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -18,6 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -25,6 +26,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -63,6 +65,11 @@ public class SmartContractDeploy extends AbstractTransaction { */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractDeploy Builder class */ @@ -72,6 +79,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + SmartContractDeploy txObj = (SmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 994f3e351..618c0b3f5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -50,6 +52,11 @@ public class SmartContractExecution extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractExecution Builder class */ @@ -57,6 +64,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + SmartContractExecution txObj = (SmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index c4e1c4af0..254d272c9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -44,12 +46,18 @@ public class ValueTransfer extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransfer Builder class */ public static class Builder extends AbstractTransaction.Builder { private String to; private String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeValueTransfer.toString()); @@ -71,6 +79,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ValueTransfer build() { return new ValueTransfer(this); } @@ -110,6 +128,7 @@ public ValueTransfer(ValueTransfer.Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -131,12 +150,44 @@ public ValueTransfer(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -260,10 +311,72 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ValueTransfer txObj = (ValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index f758b4000..517c19b16 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -50,6 +52,11 @@ public class ValueTransferMemo extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransferMemo Builder class */ @@ -57,6 +64,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ValueTransferMemo txObj = (ValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java index 673697d23..9139adf0c 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java @@ -62,7 +62,6 @@ public void checkTxObject(Transaction.TransactionData expected, AbstractTransact assertEquals(expected.getFrom(), actual.getFrom()); assertEquals(expected.getGas(), actual.getGas()); assertEquals(expected.getNonce(), actual.getNonce()); - assertEquals(expected.getGasPrice(), actual.getGasPrice()); assertEquals(expected.getHash(), actual.getTransactionHash()); } From da06a9dc3d72f5c3653b4f479a1fdff5efd1d3c3 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:49:54 +0900 Subject: [PATCH 70/89] Must throw error if tx type is different. --- .../java/com/klaytn/caver/transaction/type/AccountUpdate.java | 2 +- .../src/main/java/com/klaytn/caver/transaction/type/Cancel.java | 2 +- .../com/klaytn/caver/transaction/type/ChainDataAnchoring.java | 2 +- .../com/klaytn/caver/transaction/type/EthereumAccessList.java | 2 +- .../caver/transaction/type/FeeDelegatedAccountUpdate.java | 2 +- .../transaction/type/FeeDelegatedAccountUpdateWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/FeeDelegatedCancel.java | 2 +- .../caver/transaction/type/FeeDelegatedCancelWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedChainDataAnchoring.java | 2 +- .../type/FeeDelegatedChainDataAnchoringWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedSmartContractDeploy.java | 2 +- .../type/FeeDelegatedSmartContractDeployWithRatio.java | 2 +- .../transaction/type/FeeDelegatedSmartContractExecution.java | 2 +- .../type/FeeDelegatedSmartContractExecutionWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransfer.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransferMemo.java | 2 +- .../type/FeeDelegatedValueTransferMemoWithRatio.java | 2 +- .../transaction/type/FeeDelegatedValueTransferWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/LegacyTransaction.java | 2 +- .../com/klaytn/caver/transaction/type/SmartContractDeploy.java | 2 +- .../klaytn/caver/transaction/type/SmartContractExecution.java | 2 +- .../java/com/klaytn/caver/transaction/type/ValueTransfer.java | 2 +- .../com/klaytn/caver/transaction/type/ValueTransferMemo.java | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 0c3387a4e..a2c9f7272 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -316,7 +316,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } AccountUpdate txObj = (AccountUpdate) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index ee06f6f0b..f7a03f4c6 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -288,7 +288,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } Cancel txObj = (Cancel) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index 169f99b53..bbb270cd7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -310,7 +310,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ChainDataAnchoring txObj = (ChainDataAnchoring) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 5cf0e8103..90017ea15 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -330,7 +330,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } EthereumAccessList txObj = (EthereumAccessList) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 5627b83dd..89e4fa4d7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -363,7 +363,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedAccountUpdate txObj = (FeeDelegatedAccountUpdate) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 994588369..fd9142531 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -378,7 +378,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedAccountUpdateWithRatio txObj = (FeeDelegatedAccountUpdateWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index e06cb5984..a1ca6f660 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -338,7 +338,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedCancel txObj = (FeeDelegatedCancel) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index deb3590ed..18f2896fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -340,7 +340,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedCancelWithRatio txObj = (FeeDelegatedCancelWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index fab6dc822..f087e775c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -361,7 +361,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedChainDataAnchoring txObj = (FeeDelegatedChainDataAnchoring) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index 626e86545..cfb0e55ba 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -366,7 +366,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedChainDataAnchoringWithRatio txObj = (FeeDelegatedChainDataAnchoringWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index 379a8af6a..0f5ead2b4 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -457,7 +457,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractDeploy txObj = (FeeDelegatedSmartContractDeploy) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index def36b72e..aa1089696 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -468,7 +468,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractDeployWithRatio txObj = (FeeDelegatedSmartContractDeployWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index 2b2613cac..e55abd983 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -408,7 +408,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractExecution txObj = (FeeDelegatedSmartContractExecution) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 5900a3785..10943d780 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -412,7 +412,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractExecutionWithRatio txObj = (FeeDelegatedSmartContractExecutionWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index cb30ed217..d43829287 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -390,7 +390,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransfer txObj = (FeeDelegatedValueTransfer) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 82b6a78e3..28f053fea 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -410,7 +410,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferMemo txObj = (FeeDelegatedValueTransferMemo) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index 663a9a929..fe8d80b47 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -417,7 +417,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferMemoWithRatio txObj = (FeeDelegatedValueTransferMemoWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index 01383e31f..491f325a0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -397,7 +397,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferWithRatio txObj = (FeeDelegatedValueTransferWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index 03a3583f0..f4b12cb26 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -367,7 +367,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } LegacyTransaction txObj = (LegacyTransaction) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index 8dbec94ae..d2d3cd851 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -396,7 +396,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } SmartContractDeploy txObj = (SmartContractDeploy) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 618c0b3f5..8f8f7bfe5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -348,7 +348,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } SmartContractExecution txObj = (SmartContractExecution) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index 254d272c9..6f5e13d66 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -327,7 +327,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ValueTransfer txObj = (ValueTransfer) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index 517c19b16..ce100ee7f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -347,7 +347,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ValueTransferMemo txObj = (ValueTransferMemo) decode; From e1ba998f75553a23ba83f9765752ebd67650db82 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:33:59 +0900 Subject: [PATCH 71/89] Remove gasPrice from AbstractTransaction * Add setter and getter for gasPrice for each transaction which have gasPrice field. * Override combineSignedRawTransaction * Hard to make common usage because now we cannot compare gasPrice field by using Abstract class. * Override fillTransaction * For common fields, use super.fillTransaction. * Override validateOptionalValues * For common fields, use super.validateOptionalValues. --- .../AbstractFeeDelegatedTransaction.java | 47 +----- ...tractFeeDelegatedWithRatioTransaction.java | 5 +- .../transaction/AbstractTransaction.java | 94 +----------- .../caver/transaction/type/AccountUpdate.java | 122 +++++++++++++++- .../klaytn/caver/transaction/type/Cancel.java | 133 ++++++++++++++++- .../transaction/type/ChainDataAnchoring.java | 132 ++++++++++++++++- .../transaction/type/EthereumAccessList.java | 120 ++++++++++++++- .../type/FeeDelegatedAccountUpdate.java | 124 +++++++++++++++- .../FeeDelegatedAccountUpdateWithRatio.java | 132 ++++++++++++++++- .../transaction/type/FeeDelegatedCancel.java | 132 ++++++++++++++++- .../type/FeeDelegatedCancelWithRatio.java | 138 +++++++++++++++++- .../type/FeeDelegatedChainDataAnchoring.java | 129 +++++++++++++++- ...eDelegatedChainDataAnchoringWithRatio.java | 130 ++++++++++++++++- .../type/FeeDelegatedSmartContractDeploy.java | 130 ++++++++++++++++- ...DelegatedSmartContractDeployWithRatio.java | 131 ++++++++++++++++- .../FeeDelegatedSmartContractExecution.java | 128 +++++++++++++++- ...egatedSmartContractExecutionWithRatio.java | 130 ++++++++++++++++- .../type/FeeDelegatedValueTransfer.java | 129 +++++++++++++++- .../type/FeeDelegatedValueTransferMemo.java | 131 ++++++++++++++++- ...eeDelegatedValueTransferMemoWithRatio.java | 129 +++++++++++++++- .../FeeDelegatedValueTransferWithRatio.java | 130 ++++++++++++++++- .../transaction/type/LegacyTransaction.java | 116 ++++++++++++++- .../transaction/type/SmartContractDeploy.java | 115 ++++++++++++++- .../type/SmartContractExecution.java | 115 ++++++++++++++- .../caver/transaction/type/ValueTransfer.java | 115 ++++++++++++++- .../transaction/type/ValueTransferMemo.java | 115 ++++++++++++++- .../transaction/TransactionHelperTest.java | 1 - 27 files changed, 2888 insertions(+), 165 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java index 7028e55bb..79d060309 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java @@ -102,14 +102,13 @@ public AbstractFeeDelegatedTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. */ - public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures); + public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures); setFeePayer(feePayer); setFeePayerSignatures(feePayerSignatures); } @@ -238,48 +237,6 @@ public void appendFeePayerSignatures(List signatureData) { setFeePayerSignatures(signatureData); } - /** - * Combines signatures and feePayerSignatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. - * When combining the signatures into a transaction instance, - * an error is thrown if the decoded transaction contains different value except signatures. - * @param rlpEncoded A List of RLP-encoded transaction strings. - * @return String - */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; - - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. - if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; - - for(String encodedStr : rlpEncoded) { - AbstractFeeDelegatedTransaction txObj = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - this.setFeePayer(txObj.getFeePayer()); - fillVariable = false; - } - } - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); - } - - return this.getRLPEncoding(); - } - /** * Returns a RLP-encoded transaction string for making fee payer's signature. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java index 79b6b0ff1..6a2aea843 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java @@ -74,15 +74,14 @@ public AbstractFeeDelegatedWithRatioTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. * @param feeRatio A fee ratio of the fee payer. */ - public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures); + public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures, feePayer, feePayerSignatures); setFeeRatio(feeRatio); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index 459be6d2e..ed9c9cd63 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -71,11 +71,6 @@ abstract public class AbstractTransaction { */ private String gas; - /** - * A unit price of gas in peb the sender will pay for a transaction fee. - */ - private String gasPrice = "0x"; - /** * Network ID */ @@ -96,7 +91,6 @@ public static class Builder { private String from; private String nonce = "0x"; - private String gasPrice = "0x"; private String chainId = "0x"; private Klay klaytnCall = null; private List signatures = new ArrayList<>(); @@ -130,16 +124,6 @@ public B setGas(BigInteger gas) { return (B) this; } - public B setGasPrice(String gasPrice) { - this.gasPrice = gasPrice; - return (B) this; - } - - public B setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - return (B) this; - } - public B setChainId(String chainId) { this.chainId = chainId; return (B) this; @@ -180,7 +164,6 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { builder.from, builder.nonce, builder.gas, - builder.gasPrice, builder.chainId, builder.signatures ); @@ -193,16 +176,14 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list */ - public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { + public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures) { setKlaytnCall(klaytnCall); setType(type); setFrom(from); setNonce(nonce); - setGasPrice(gasPrice); setGas(gas); setChainId(chainId); setSignatures(signatures); @@ -360,33 +341,8 @@ public void appendSignatures(List signatureData) { * @param rlpEncoded A List of RLP-encoded transaction strings. * @return String */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; + public abstract String combineSignedRawTransactions(List rlpEncoded); - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; - - for(String encodedStr : rlpEncoded) { - AbstractTransaction txObj = TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - fillVariable = false; - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - } - - return this.getRLPEncoding(); - } /** * Returns a RawTransaction(RLP-encoded transaction string) @@ -445,15 +401,10 @@ public void fillTransaction() throws IOException{ if(this.chainId.equals("0x")) { this.chainId = klaytnCall.getChainID().send().getResult(); } - - if(this.gasPrice.equals("0x")) { - this.gasPrice = klaytnCall.getGasPrice().send().getResult(); - } - } - if(this.nonce.equals("0x") || this.chainId.equals("0x") || this.gasPrice.equals("0x")) { - throw new RuntimeException("Cannot fill transaction data.(nonce, chainId, gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + if(this.nonce.equals("0x") || this.chainId.equals("0x")) { + throw new RuntimeException("Cannot fill transaction data.(nonce, chainId). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); } } @@ -468,7 +419,6 @@ public boolean compareTxField(AbstractTransaction txObj, boolean checkSig) { if(!this.getFrom().toLowerCase().equals(txObj.getFrom().toLowerCase())) return false; if(!Numeric.toBigInt(this.getNonce()).equals(Numeric.toBigInt(txObj.getNonce()))) return false; if(!Numeric.toBigInt(this.getGas()).equals(Numeric.toBigInt(txObj.getGas()))) return false; - if(!Numeric.toBigInt(this.getGasPrice()).equals(Numeric.toBigInt(txObj.getGasPrice()))) return false; if(checkSig) { List dataList = this.getSignatures(); @@ -493,10 +443,6 @@ public void validateOptionalValues(boolean checkChainID) { throw new RuntimeException("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); } - if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { - throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); - } - if(checkChainID) { if(this.getChainId() == null || this.getChainId().isEmpty() || this.getChainId().equals("0x")) { throw new RuntimeException("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); @@ -612,14 +558,6 @@ public String getGas() { return gas; } - /** - * Getter function for gas price - * @return String - */ - public String getGasPrice() { - return gasPrice; - } - /** * Getter function for chain id * @return String @@ -710,30 +648,6 @@ public void setNonce(BigInteger nonce) { setNonce(Numeric.toHexStringWithPrefix(nonce)); } - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(String gasPrice) { - if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { - gasPrice = "0x"; - } - - if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { - throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); - } - - this.gasPrice = gasPrice; - } - - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - } - /** * Setter function for chain id. * @param chainId A network id. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 78359635d..0c3387a4e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -19,11 +19,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -40,11 +43,17 @@ public class AccountUpdate extends AbstractTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * AccountUpdate Builder class */ public static class Builder extends AbstractTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeAccountUpdate.toString()); @@ -55,6 +64,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public AccountUpdate build() { return new AccountUpdate(this); } @@ -92,6 +111,7 @@ public static AccountUpdate create(Klay klaytnCall, String from, String nonce, S public AccountUpdate(AccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -112,11 +132,43 @@ public AccountUpdate(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -241,10 +293,78 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getAccount().getRLPEncodingAccountKey().equals(txObj.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + AccountUpdate txObj = (AccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index daf278680..ee06f6f0b 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -18,11 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,15 +36,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypecancel to see more detail. */ public class Cancel extends AbstractTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * Cancel Builder class */ public static class Builder extends AbstractTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public Cancel build() { return new Cancel(this); } @@ -77,6 +96,7 @@ public static Cancel create(Klay klaytnCall, String from, String nonce, String g */ public Cancel(Cancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -90,9 +110,51 @@ public Cancel(Cancel.Builder builder) { * @param signatures A Signature list */ public Cancel(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { - super(klaytnCall, TransactionType.TxTypeCancel.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeCancel.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setGasPrice(gasPrice); } + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded Cancel string. * @param rlpEncoded RLP-encoded Cancel string @@ -202,7 +264,76 @@ public String getCommonRLPEncodingForSignature() { public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!super.compareTxField(obj, checkSig)) return false; if(!(obj instanceof Cancel)) return false; + Cancel txObj = (Cancel) obj; + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + Cancel txObj = (Cancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index ee79992f3..169f99b53 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -39,11 +41,17 @@ public class ChainDataAnchoring extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ChainDataAnchoring Builder class */ public static class Builder extends AbstractTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeChainDataAnchoring.toString()); @@ -54,6 +62,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ChainDataAnchoring build() { return new ChainDataAnchoring(this); } @@ -91,6 +109,7 @@ public static ChainDataAnchoring create(Klay klaytnCall, String from, String non public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -105,10 +124,52 @@ public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { * @param input Data of the service chain. */ public ChainDataAnchoring(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String input) { - super(klaytnCall, TransactionType.TxTypeChainDataAnchoring.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeChainDataAnchoring.toString(), + from, + nonce, + gas, + chainId, + signatures + ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded ChainDataAnchoring string. * @param rlpEncoded RLP-encoded ChainDataAnchoring string @@ -226,10 +287,79 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { ChainDataAnchoring txObj = (ChainDataAnchoring)obj; if(!this.getInput().equals(txObj.getInput())) return false; + if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ChainDataAnchoring txObj = (ChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 558a39d9b..5cf0e8103 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -19,6 +19,7 @@ import com.klaytn.caver.account.AccountKeyRoleBased; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.utils.AccessList; @@ -62,6 +63,11 @@ public class EthereumAccessList extends AbstractTransaction { */ AccessList accessList; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * EthereumAccessList Builder class */ @@ -70,6 +76,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + EthereumAccessList txObj = (EthereumAccessList) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } /** * Decodes a RLP-encoded EthereumAccessList string. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 55d9e6528..5627b83dd 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -19,12 +19,16 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -41,11 +45,17 @@ public class FeeDelegatedAccountUpdate extends AbstractFeeDelegatedTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdate Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdate.toString()); @@ -56,6 +66,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdate build() { return new FeeDelegatedAccountUpdate(this); } @@ -95,6 +115,7 @@ public static FeeDelegatedAccountUpdate create(Klay klaytnCall, String from, Str public FeeDelegatedAccountUpdate(FeeDelegatedAccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -117,13 +138,45 @@ public FeeDelegatedAccountUpdate(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -293,10 +346,79 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedAccountUpdate txObj = (FeeDelegatedAccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 8a76e103f..994588369 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -18,13 +18,17 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -41,11 +45,17 @@ public class FeeDelegatedAccountUpdateWithRatio extends AbstractFeeDelegatedWith */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdateWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdateWithRatio.toString()); @@ -56,6 +66,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdateWithRatio build() { return new FeeDelegatedAccountUpdateWithRatio(this); } @@ -96,6 +116,7 @@ public static FeeDelegatedAccountUpdateWithRatio create(Klay klaytnCall, String public FeeDelegatedAccountUpdateWithRatio(Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -119,7 +140,6 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -127,8 +147,42 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n feeRatio ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedAccountUpdateWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedAccountUpdateWithRatio string. @@ -300,10 +354,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedAccountUpdateWithRatio txObj = (FeeDelegatedAccountUpdateWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index acec35cb8..e06cb5984 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -19,12 +19,15 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,15 +38,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedcancel to see more detail. */ public class FeeDelegatedCancel extends AbstractFeeDelegatedTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancel Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancel build() { return new FeeDelegatedCancel(this); } @@ -81,6 +100,7 @@ public static FeeDelegatedCancel create(Klay klaytnCall, String from, String non */ public FeeDelegatedCancel(FeeDelegatedCancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -102,12 +122,44 @@ public FeeDelegatedCancel(Klay klaytnCall, String from, String nonce, String gas from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -261,7 +313,85 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancel)) return false; + FeeDelegatedCancel feeDelegatedCancel = (FeeDelegatedCancel) txObj; + if (!this.getGasPrice().equals(feeDelegatedCancel.getGasPrice())) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedCancel txObj = (FeeDelegatedCancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index c85c1bbf7..deb3590ed 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -17,13 +17,17 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,15 +38,31 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedcancelwithratio to see more detail. */ public class FeeDelegatedCancelWithRatio extends AbstractFeeDelegatedWithRatioTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancelWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancelWithRatio build() { return new FeeDelegatedCancelWithRatio(this); } @@ -81,6 +101,7 @@ public static FeeDelegatedCancelWithRatio create(Klay klaytnCall, String from, S */ public FeeDelegatedCancelWithRatio(Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -97,7 +118,51 @@ public FeeDelegatedCancelWithRatio(Builder builder) { * @param feeRatio A fee ratio of the fee payer. */ public FeeDelegatedCancelWithRatio(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures, feeRatio); + super( + klaytnCall, + TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), + from, + nonce, + gas, + chainId, + signatures, + feePayer, + feePayerSignatures, + feeRatio + ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -257,7 +322,78 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancelWithRatio)) return false; + FeeDelegatedCancelWithRatio feeDelegatedCancelWithRatio = (FeeDelegatedCancelWithRatio) txObj; + if (!this.getGasPrice().equals(feeDelegatedCancelWithRatio.getGasPrice())) return false; return true; } + + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedCancelWithRatio txObj = (FeeDelegatedCancelWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index ee7436d53..fab6dc822 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -42,11 +44,17 @@ public class FeeDelegatedChainDataAnchoring extends AbstractFeeDelegatedTransact */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoring Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoring.toString()); @@ -57,6 +65,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoring build() { return new FeeDelegatedChainDataAnchoring(this); } @@ -96,6 +114,7 @@ public static FeeDelegatedChainDataAnchoring create(Klay klaytnCall, String from public FeeDelegatedChainDataAnchoring(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,13 +137,45 @@ public FeeDelegatedChainDataAnchoring(Klay klaytnCall, String from, String nonce from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -286,10 +337,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che FeeDelegatedChainDataAnchoring feeDelegatedChainDataAnchoring = (FeeDelegatedChainDataAnchoring)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoring.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoring.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedChainDataAnchoring txObj = (FeeDelegatedChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index a5dba7c2e..626e86545 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -40,11 +43,17 @@ public class FeeDelegatedChainDataAnchoringWithRatio extends AbstractFeeDelegate */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoringWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoringWithRatio.toString()); @@ -55,6 +64,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoringWithRatio build() { return new FeeDelegatedChainDataAnchoringWithRatio(this); } @@ -95,6 +114,7 @@ public static FeeDelegatedChainDataAnchoringWithRatio create(Klay klaytnCall, St public FeeDelegatedChainDataAnchoringWithRatio(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,7 +138,6 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -126,6 +145,39 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str feeRatio ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -290,10 +342,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo FeeDelegatedChainDataAnchoringWithRatio feeDelegatedChainDataAnchoringWithRatio = (FeeDelegatedChainDataAnchoringWithRatio)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoringWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoringWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedChainDataAnchoringWithRatio txObj = (FeeDelegatedChainDataAnchoringWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index f7d073991..379a8af6a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -18,6 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +38,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractdeploy to see more detail. */ public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -64,6 +65,11 @@ public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransac */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeploy Builder class */ @@ -73,6 +79,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractDeploy txObj = (FeeDelegatedSmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index ad7ff4d53..def36b72e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -64,6 +67,11 @@ public class FeeDelegatedSmartContractDeployWithRatio extends AbstractFeeDelegat */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeployWithRatio Builder class */ @@ -73,6 +81,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String input; boolean humanReadable = false; String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractDeployWithRatio.toString()); @@ -113,6 +122,16 @@ public Builder setCodeFormat(BigInteger codeFormat) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractDeployWithRatio build() { return new FeeDelegatedSmartContractDeployWithRatio(this); } @@ -161,6 +180,7 @@ public FeeDelegatedSmartContractDeployWithRatio(Builder builder) { setInput(builder.input); setHumanReadable(builder.humanReadable); setCodeFormat(builder.codeFormat); + setGasPrice(builder.gasPrice); } /** @@ -188,7 +208,6 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -200,8 +219,42 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St setInput(input); setHumanReadable(humanReadable); setCodeFormat(codeFormat); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. @@ -391,10 +444,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getInput().equals(feeDelegatedSmartContractDeployWithRatio.getInput())) return false; if(this.getHumanReadable() != feeDelegatedSmartContractDeployWithRatio.getHumanReadable()) return false; if(!this.getCodeFormat().equals(feeDelegatedSmartContractDeployWithRatio.getCodeFormat())) return false; + if(!this.getGasPrice().equals(feeDelegatedSmartContractDeployWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractDeployWithRatio txObj = (FeeDelegatedSmartContractDeployWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index 594be3894..2b2613cac 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +53,11 @@ public class FeeDelegatedSmartContractExecution extends AbstractFeeDelegatedTran */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecution Builder class */ @@ -58,6 +65,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractExecution txObj = (FeeDelegatedSmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 43e8f3a78..5900a3785 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -46,6 +49,11 @@ public class FeeDelegatedSmartContractExecutionWithRatio extends AbstractFeeDele */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecutionWithRatio Builder class */ @@ -53,6 +61,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value = "0x00"; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractExecutionWithRatio.toString()); @@ -78,6 +87,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractExecutionWithRatio build() { return new FeeDelegatedSmartContractExecutionWithRatio(this); } @@ -122,6 +141,7 @@ public FeeDelegatedSmartContractExecutionWithRatio(Builder builder) { setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -147,7 +167,6 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -157,8 +176,42 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } + /** * Decodes a RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. @@ -335,10 +388,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedSmartContractExecutionWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedSmartContractExecutionWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedSmartContractExecutionWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedSmartContractExecutionWithRatio txObj = (FeeDelegatedSmartContractExecutionWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index fd3b40a1b..cb30ed217 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.crypto.KlaySignatureData; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -47,12 +49,18 @@ public class FeeDelegatedValueTransfer extends AbstractFeeDelegatedTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransfer Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransfer.toString()); @@ -73,6 +81,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransfer build() { return new FeeDelegatedValueTransfer(this); } @@ -114,6 +132,7 @@ public FeeDelegatedValueTransfer(Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -137,7 +156,6 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -145,6 +163,39 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -315,10 +366,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransfer.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransfer.getValue()))) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransfer.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransfer txObj = (FeeDelegatedValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 445e354fa..82b6a78e3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -19,6 +19,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +38,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfermemo to see more detail. */ public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -52,6 +53,11 @@ public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransacti */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -59,6 +65,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferMemo txObj = (FeeDelegatedValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index fa2388e52..663a9a929 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +54,11 @@ public class FeeDelegatedValueTransferMemoWithRatio extends AbstractFeeDelegated */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -58,6 +66,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferMemoWithRatio.toString()); @@ -82,6 +91,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferMemoWithRatio build() { return new FeeDelegatedValueTransferMemoWithRatio(this); } @@ -126,6 +145,7 @@ public FeeDelegatedValueTransferMemoWithRatio(FeeDelegatedValueTransferMemoWithR setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -151,7 +171,6 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -161,6 +180,39 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -341,10 +393,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferMemoWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedValueTransferMemoWithRatio.getInput())) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransferMemoWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferMemoWithRatio txObj = (FeeDelegatedValueTransferMemoWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index e3b646712..01383e31f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -17,7 +17,9 @@ package com.klaytn.caver.transaction.type; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +39,6 @@ * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransferwithratio to see more detail. */ public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWithRatioTransaction { - /** * The account address that will receive the transferred value. */ @@ -47,12 +49,18 @@ public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWith */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferWithRatio.toString()); @@ -73,6 +81,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferWithRatio build() { return new FeeDelegatedValueTransferWithRatio(this); } @@ -115,6 +133,7 @@ public FeeDelegatedValueTransferWithRatio(FeeDelegatedValueTransferWithRatio.Bui super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -139,7 +158,6 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -148,6 +166,39 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -322,10 +373,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getValue()))) return false; + if(!this.getGasPrice().equals(feeDelegatedValueTransferWithRatio.getGasPrice())) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + FeeDelegatedValueTransferWithRatio txObj = (FeeDelegatedValueTransferWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index 025c8a641..03a3583f0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -18,11 +18,13 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -43,6 +45,11 @@ public class LegacyTransaction extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * LegacyTransaction Builder class */ @@ -50,6 +57,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + LegacyTransaction txObj = (LegacyTransaction) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index 925619024..8dbec94ae 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -18,6 +18,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -25,6 +26,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -63,6 +65,11 @@ public class SmartContractDeploy extends AbstractTransaction { */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractDeploy Builder class */ @@ -72,6 +79,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + SmartContractDeploy txObj = (SmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 994f3e351..618c0b3f5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -50,6 +52,11 @@ public class SmartContractExecution extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractExecution Builder class */ @@ -57,6 +64,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + SmartContractExecution txObj = (SmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index c4e1c4af0..254d272c9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -44,12 +46,18 @@ public class ValueTransfer extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransfer Builder class */ public static class Builder extends AbstractTransaction.Builder { private String to; private String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeValueTransfer.toString()); @@ -71,6 +79,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ValueTransfer build() { return new ValueTransfer(this); } @@ -110,6 +128,7 @@ public ValueTransfer(ValueTransfer.Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -131,12 +150,44 @@ public ValueTransfer(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -260,10 +311,72 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ValueTransfer txObj = (ValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index f758b4000..517c19b16 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -18,12 +18,14 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -50,6 +52,11 @@ public class ValueTransferMemo extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransferMemo Builder class */ @@ -57,6 +64,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + continue; + } + ValueTransferMemo txObj = (ValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java index 673697d23..9139adf0c 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java @@ -62,7 +62,6 @@ public void checkTxObject(Transaction.TransactionData expected, AbstractTransact assertEquals(expected.getFrom(), actual.getFrom()); assertEquals(expected.getGas(), actual.getGas()); assertEquals(expected.getNonce(), actual.getNonce()); - assertEquals(expected.getGasPrice(), actual.getGasPrice()); assertEquals(expected.getHash(), actual.getTransactionHash()); } From 19ebc1e9e07a0d50bf3ae3520e9fbc69d0acf32b Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:49:54 +0900 Subject: [PATCH 72/89] Must throw error if tx type is different. --- .../java/com/klaytn/caver/transaction/type/AccountUpdate.java | 2 +- .../src/main/java/com/klaytn/caver/transaction/type/Cancel.java | 2 +- .../com/klaytn/caver/transaction/type/ChainDataAnchoring.java | 2 +- .../com/klaytn/caver/transaction/type/EthereumAccessList.java | 2 +- .../caver/transaction/type/FeeDelegatedAccountUpdate.java | 2 +- .../transaction/type/FeeDelegatedAccountUpdateWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/FeeDelegatedCancel.java | 2 +- .../caver/transaction/type/FeeDelegatedCancelWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedChainDataAnchoring.java | 2 +- .../type/FeeDelegatedChainDataAnchoringWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedSmartContractDeploy.java | 2 +- .../type/FeeDelegatedSmartContractDeployWithRatio.java | 2 +- .../transaction/type/FeeDelegatedSmartContractExecution.java | 2 +- .../type/FeeDelegatedSmartContractExecutionWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransfer.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransferMemo.java | 2 +- .../type/FeeDelegatedValueTransferMemoWithRatio.java | 2 +- .../transaction/type/FeeDelegatedValueTransferWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/LegacyTransaction.java | 2 +- .../com/klaytn/caver/transaction/type/SmartContractDeploy.java | 2 +- .../klaytn/caver/transaction/type/SmartContractExecution.java | 2 +- .../java/com/klaytn/caver/transaction/type/ValueTransfer.java | 2 +- .../com/klaytn/caver/transaction/type/ValueTransferMemo.java | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 0c3387a4e..a2c9f7272 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -316,7 +316,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } AccountUpdate txObj = (AccountUpdate) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index ee06f6f0b..f7a03f4c6 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -288,7 +288,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } Cancel txObj = (Cancel) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index 169f99b53..bbb270cd7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -310,7 +310,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ChainDataAnchoring txObj = (ChainDataAnchoring) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 5cf0e8103..90017ea15 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -330,7 +330,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } EthereumAccessList txObj = (EthereumAccessList) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 5627b83dd..89e4fa4d7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -363,7 +363,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedAccountUpdate txObj = (FeeDelegatedAccountUpdate) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 994588369..fd9142531 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -378,7 +378,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedAccountUpdateWithRatio txObj = (FeeDelegatedAccountUpdateWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index e06cb5984..a1ca6f660 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -338,7 +338,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedCancel txObj = (FeeDelegatedCancel) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index deb3590ed..18f2896fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -340,7 +340,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedCancelWithRatio txObj = (FeeDelegatedCancelWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index fab6dc822..f087e775c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -361,7 +361,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedChainDataAnchoring txObj = (FeeDelegatedChainDataAnchoring) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index 626e86545..cfb0e55ba 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -366,7 +366,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedChainDataAnchoringWithRatio txObj = (FeeDelegatedChainDataAnchoringWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index 379a8af6a..0f5ead2b4 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -457,7 +457,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractDeploy txObj = (FeeDelegatedSmartContractDeploy) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index def36b72e..aa1089696 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -468,7 +468,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractDeployWithRatio txObj = (FeeDelegatedSmartContractDeployWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index 2b2613cac..e55abd983 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -408,7 +408,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractExecution txObj = (FeeDelegatedSmartContractExecution) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 5900a3785..10943d780 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -412,7 +412,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedSmartContractExecutionWithRatio txObj = (FeeDelegatedSmartContractExecutionWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index cb30ed217..d43829287 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -390,7 +390,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransfer txObj = (FeeDelegatedValueTransfer) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 82b6a78e3..28f053fea 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -410,7 +410,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferMemo txObj = (FeeDelegatedValueTransferMemo) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index 663a9a929..fe8d80b47 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -417,7 +417,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferMemoWithRatio txObj = (FeeDelegatedValueTransferMemoWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index 01383e31f..491f325a0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -397,7 +397,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } FeeDelegatedValueTransferWithRatio txObj = (FeeDelegatedValueTransferWithRatio) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index 03a3583f0..f4b12cb26 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -367,7 +367,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } LegacyTransaction txObj = (LegacyTransaction) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index 8dbec94ae..d2d3cd851 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -396,7 +396,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } SmartContractDeploy txObj = (SmartContractDeploy) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 618c0b3f5..8f8f7bfe5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -348,7 +348,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } SmartContractExecution txObj = (SmartContractExecution) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index 254d272c9..6f5e13d66 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -327,7 +327,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ValueTransfer txObj = (ValueTransfer) decode; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index 517c19b16..ce100ee7f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -347,7 +347,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { for(String encodedStr : rlpEncoded) { AbstractTransaction decode = TransactionDecoder.decode(encodedStr); if (!decode.getType().equals(this.getType())) { - continue; + throw new RuntimeException("Transactions containing different information cannot be combined."); } ValueTransferMemo txObj = (ValueTransferMemo) decode; From 25db8b8f9a08410686e2685f7175622d2b542dff Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:33:59 +0900 Subject: [PATCH 73/89] Remove gasPrice from AbstractTransaction * Add setter and getter for gasPrice for each transaction which have gasPrice field. * Override combineSignedRawTransaction * Hard to make common usage because now we cannot compare gasPrice field by using Abstract class. * Override fillTransaction * For common fields, use super.fillTransaction. * Override validateOptionalValues * For common fields, use super.validateOptionalValues. --- core/ipfs.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 core/ipfs.txt diff --git a/core/ipfs.txt b/core/ipfs.txt new file mode 100644 index 000000000..e3a005910 --- /dev/null +++ b/core/ipfs.txt @@ -0,0 +1 @@ +This is IPFS test. \ No newline at end of file From fa1f5d41d24d86f27d1455979637b1de7702022a Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 14:57:52 +0900 Subject: [PATCH 74/89] ipfs.txt added merged by accdent during rebase --- core/ipfs.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 core/ipfs.txt diff --git a/core/ipfs.txt b/core/ipfs.txt deleted file mode 100644 index e3a005910..000000000 --- a/core/ipfs.txt +++ /dev/null @@ -1 +0,0 @@ -This is IPFS test. \ No newline at end of file From 84e784545ce7c53e2b895d14d374ff97cfbccae5 Mon Sep 17 00:00:00 2001 From: Denver Date: Fri, 4 Mar 2022 15:55:13 +0900 Subject: [PATCH 75/89] Compare gasPrice as BigInteger --- .../java/com/klaytn/caver/transaction/type/AccountUpdate.java | 2 +- .../src/main/java/com/klaytn/caver/transaction/type/Cancel.java | 2 +- .../com/klaytn/caver/transaction/type/ChainDataAnchoring.java | 2 +- .../com/klaytn/caver/transaction/type/EthereumAccessList.java | 2 +- .../caver/transaction/type/FeeDelegatedAccountUpdate.java | 2 +- .../transaction/type/FeeDelegatedAccountUpdateWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/FeeDelegatedCancel.java | 2 +- .../caver/transaction/type/FeeDelegatedCancelWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedChainDataAnchoring.java | 2 +- .../type/FeeDelegatedChainDataAnchoringWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedSmartContractDeploy.java | 2 +- .../type/FeeDelegatedSmartContractDeployWithRatio.java | 2 +- .../transaction/type/FeeDelegatedSmartContractExecution.java | 2 +- .../type/FeeDelegatedSmartContractExecutionWithRatio.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransfer.java | 2 +- .../caver/transaction/type/FeeDelegatedValueTransferMemo.java | 2 +- .../type/FeeDelegatedValueTransferMemoWithRatio.java | 2 +- .../transaction/type/FeeDelegatedValueTransferWithRatio.java | 2 +- .../com/klaytn/caver/transaction/type/LegacyTransaction.java | 2 +- .../com/klaytn/caver/transaction/type/SmartContractDeploy.java | 2 +- .../klaytn/caver/transaction/type/SmartContractExecution.java | 2 +- .../java/com/klaytn/caver/transaction/type/ValueTransfer.java | 2 +- .../com/klaytn/caver/transaction/type/ValueTransferMemo.java | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index a2c9f7272..20af3935d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -293,7 +293,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getAccount().getRLPEncodingAccountKey().equals(txObj.getAccount().getRLPEncodingAccountKey())) { return false; } - if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index f7a03f4c6..9feaf8ce1 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -265,7 +265,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!super.compareTxField(obj, checkSig)) return false; if(!(obj instanceof Cancel)) return false; Cancel txObj = (Cancel) obj; - if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index bbb270cd7..afb6ecd0b 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -287,7 +287,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { ChainDataAnchoring txObj = (ChainDataAnchoring)obj; if(!this.getInput().equals(txObj.getInput())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 90017ea15..b3e70f440 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -307,7 +307,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if (!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if (!this.getInput().equals(txObj.getInput())) return false; if (!this.getAccessList().equals(txObj.getAccessList())) return false; - if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 89e4fa4d7..ad1b4ba53 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -346,7 +346,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } - if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedAccountUpdate.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index fd9142531..12ad9eaa7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -354,7 +354,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } - if (!this.getGasPrice().equals(feeDelegatedAccountUpdate.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedAccountUpdate.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index a1ca6f660..ef72cb9a7 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -314,7 +314,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancel)) return false; FeeDelegatedCancel feeDelegatedCancel = (FeeDelegatedCancel) txObj; - if (!this.getGasPrice().equals(feeDelegatedCancel.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedCancel.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index 18f2896fa..23df30e82 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -323,7 +323,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancelWithRatio)) return false; FeeDelegatedCancelWithRatio feeDelegatedCancelWithRatio = (FeeDelegatedCancelWithRatio) txObj; - if (!this.getGasPrice().equals(feeDelegatedCancelWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedCancelWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index f087e775c..f8371bbd5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -337,7 +337,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che FeeDelegatedChainDataAnchoring feeDelegatedChainDataAnchoring = (FeeDelegatedChainDataAnchoring)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoring.getInput())) return false; - if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoring.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedChainDataAnchoring.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index cfb0e55ba..62ed189e0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -342,7 +342,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo FeeDelegatedChainDataAnchoringWithRatio feeDelegatedChainDataAnchoringWithRatio = (FeeDelegatedChainDataAnchoringWithRatio)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoringWithRatio.getInput())) return false; - if(!this.getGasPrice().equals(feeDelegatedChainDataAnchoringWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedChainDataAnchoringWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index 0f5ead2b4..6e8d371c0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -433,7 +433,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getInput().equals(feeDelegatedSmartContractDeploy.getInput())) return false; if(this.getHumanReadable() != feeDelegatedSmartContractDeploy.getHumanReadable()) return false; if(!this.getCodeFormat().equals(feeDelegatedSmartContractDeploy.getCodeFormat())) return false; - if(!this.getGasPrice().equals(feeDelegatedSmartContractDeploy.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedSmartContractDeploy.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index aa1089696..5a48f7eca 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -444,7 +444,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getInput().equals(feeDelegatedSmartContractDeployWithRatio.getInput())) return false; if(this.getHumanReadable() != feeDelegatedSmartContractDeployWithRatio.getHumanReadable()) return false; if(!this.getCodeFormat().equals(feeDelegatedSmartContractDeployWithRatio.getCodeFormat())) return false; - if(!this.getGasPrice().equals(feeDelegatedSmartContractDeployWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedSmartContractDeployWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index e55abd983..bae79273d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -384,7 +384,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction obj, boolean check if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if(!this.getInput().equals(txObj.getInput())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 10943d780..79f2e63ec 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -388,7 +388,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedSmartContractExecutionWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedSmartContractExecutionWithRatio.getInput())) return false; - if(!this.getGasPrice().equals(feeDelegatedSmartContractExecutionWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index d43829287..2a1d205a0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -366,7 +366,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransfer.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransfer.getValue()))) return false; - if(!this.getGasPrice().equals(feeDelegatedValueTransfer.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransfer.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 28f053fea..af0b57e00 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -386,7 +386,7 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction obj, boolean check if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if(!this.getInput().equals(txObj.getInput())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index fe8d80b47..35085ba2a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -393,7 +393,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferMemoWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedValueTransferMemoWithRatio.getInput())) return false; - if(!this.getGasPrice().equals(feeDelegatedValueTransferMemoWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index 491f325a0..b6d5c2f80 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -373,7 +373,7 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getValue()))) return false; - if(!this.getGasPrice().equals(feeDelegatedValueTransferWithRatio.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index f4b12cb26..d1bbf76bb 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -351,7 +351,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if(!this.getInput().equals(txObj.getInput())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index d2d3cd851..d918979df 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -380,7 +380,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getInput().equals(txObj.getInput())) return false; if(this.getHumanReadable() != txObj.getHumanReadable()) return false; if(!this.getCodeFormat().equals(txObj.getCodeFormat())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 8f8f7bfe5..bffdcfee9 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -332,7 +332,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if(!this.getInput().equals(txObj.getInput())) return false; - if(!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index 6f5e13d66..4b68fa400 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -311,7 +311,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; - if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index ce100ee7f..e9e3a9ad6 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -331,7 +331,7 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; if(!this.getInput().equals(txObj.getInput())) return false; - if (!this.getGasPrice().equals(txObj.getGasPrice())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } From 6cf456bd3a92279724b4e415a35e5ce8efec8e66 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 16:51:23 +0900 Subject: [PATCH 76/89] implemented isEthereumTypedTransaction(). --- .../caver/transaction/TransactionHelper.java | 24 ++++++++++++++----- .../transaction/type/TransactionType.java | 3 ++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java index 811967f73..c0e11911a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java @@ -22,9 +22,10 @@ import java.io.IOException; import java.util.List; -import java.util.Objects; +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeLegacyTransaction; import static com.klaytn.caver.transaction.type.TransactionType.TxTypeEthereumAccessList; +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeEthereumDynamicFee; /** * This class is a helper class provides methods that handles Transaction object comfortably. @@ -96,7 +97,7 @@ public static List recoverFeePayerPublicKeys(String rawTx) { * @return */ public static boolean isEthereumTransaction(int type) { - if (type == TransactionType.TxTypeLegacyTransaction.getType() || type == TxTypeEthereumAccessList.getType()) { + if (type == TxTypeLegacyTransaction.getType() || type == TxTypeEthereumAccessList.getType() || type == TxTypeEthereumDynamicFee.getType()) { return true; } return false; @@ -108,10 +109,21 @@ public static boolean isEthereumTransaction(int type) { * @return */ public static boolean isEthereumTransaction(String type) { - if ( - Objects.equals(type, TransactionType.TxTypeLegacyTransaction.toString()) || - Objects.equals(type, TransactionType.TxTypeEthereumAccessList.toString()) - ) { + if(type.equals(TransactionType.TxTypeLegacyTransaction.toString()) || type.equals(TxTypeEthereumAccessList.toString()) || type.equals(TxTypeEthereumDynamicFee.toString())) { + return true; + } + return false; + } + + public static boolean isEthereumTypedTransaction(int type) { + if(type == TxTypeEthereumAccessList.getType() || type == TxTypeEthereumDynamicFee.getType()) { + return true; + } + return false; + } + + public static boolean isEthereumTypedTransaction(String type) { + if(type.equals(TxTypeEthereumAccessList.toString()) || type.equals(TxTypeEthereumDynamicFee.toString())) { return true; } return false; diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 68f2770a8..d213e4e5a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -49,7 +49,8 @@ public enum TransactionType { TxTypeFeeDelegatedChainDataAnchoring(0x49), TxTypeFeeDelegatedChainDataAnchoringWithRatio(0x4a), - TxTypeEthereumAccessList(0x7801); + TxTypeEthereumAccessList(0x7801), + TxTypeEthereumDynamicFee(0x7802); int type; From 1e55ecd12b255670316ce7eb137a71a555cf7044 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 16:52:04 +0900 Subject: [PATCH 77/89] Implemented recoverPublicKeyWithEthereumTypedTransaction() --- .../transaction/AbstractTransaction.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index 459be6d2e..1c5995d15 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -536,6 +536,11 @@ public List refineSignature(List signatureDataList */ public List recoverPublicKeys() { try { + // If it is EthereumTyped transaction(EthereumAccessList, EthereumDynamicFee), call recoverPublicKeysWithEthereumTypedTransaction. + if(TransactionHelper.isEthereumTypedTransaction(this.getType())) { + return recoverPublicKeysWithEthereumTypedTransaction(); + } + if(Utils.isEmptySig(this.getSignatures())) { throw new RuntimeException("Failed to recover public keys from signatures: signatures is empty."); } @@ -564,6 +569,23 @@ public List recoverPublicKeys() { } } + private List recoverPublicKeysWithEthereumTypedTransaction() throws SignatureException{ + if(Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Failed to recover public keys from signatures: signatures is empty."); + } + + String sigHash = TransactionHasher.getHashForSignature(this); + + List publicKeyList = new ArrayList<>(); + for(SignatureData signatureData : this.getSignatures()) { + if(Numeric.toBigInt(signatureData.getV()).compareTo(BigInteger.ZERO) != 0 && Numeric.toBigInt(signatureData.getV()).compareTo(BigInteger.ONE) != 0) { + throw new RuntimeException("Invalid Signature data : the v value must have 0 or 1."); + } + publicKeyList.add(Utils.recoverPublicKey(sigHash, signatureData, true)); + } + return publicKeyList; + } + /** * Getter function for klaytnRPC * @return Klay From 28c13726335959d60a4ef1918fb250df56d3ecc2 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 16:52:19 +0900 Subject: [PATCH 78/89] add a test. --- .../transaction/EthereumAccessListTest.java | 42 +++++++++++++++++++ .../klaytn/caver/validator/ValidatorTest.java | 39 +++++++++++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java index 72fd9a395..82d82a03a 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -21,6 +21,7 @@ import com.klaytn.caver.transaction.TransactionHasher; import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.transaction.type.LegacyTransaction; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.transaction.utils.AccessTuple; @@ -1668,4 +1669,45 @@ public void throwException_roleBasedKeyring() throws IOException { } } + public static class recoverPublicKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void recoverPublicKey() { + String expectedPublicKey = "0x3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3"; + SignatureData signatureData = new SignatureData( + "0x1", + "0xbfc80a874c43b71b67c68fa5927d1443407f31aef4ec6369bbecdb76fc39b0c0", + "0x193e62c1dd63905aee7073958675dcb45d78c716a9a286b54a496e82cb762f26" + ); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + )) + ); + + EthereumAccessList tx = new EthereumAccessList.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x7b65b75d204abed71587c9e519a89277766ee1d0") + .setValue("0xa") + .setChainId("0x2") + .setGasPrice("0x19") + .setNonce("0x4d2") + .setGas("0xf4240") + .setInput("0x31323334") + .setAccessList(accessList) + .setSignatures(signatureData) + .build(); + + List publicKeys = tx.recoverPublicKeys(); + assertEquals(expectedPublicKey, publicKeys.get(0)); + } + } + } diff --git a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java index 8e4c559fe..48f429418 100644 --- a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java +++ b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java @@ -22,10 +22,10 @@ import com.klaytn.caver.account.AccountKeyLegacy; import com.klaytn.caver.methods.response.AccountKey; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.type.AccountUpdate; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransfer; -import com.klaytn.caver.transaction.type.LegacyTransaction; -import com.klaytn.caver.transaction.type.ValueTransfer; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.wallet.keyring.KeyringFactory; import com.klaytn.caver.wallet.keyring.SignatureData; import com.klaytn.caver.wallet.keyring.SingleKeyring; @@ -1570,6 +1570,7 @@ public void withInvalidFeePayerSignature() throws IOException { public static class validateTransaction { static ValueTransfer.Builder txBuilder; static FeeDelegatedValueTransfer.Builder fdTxBuilder; + static EthereumAccessList.Builder accessTxBuilder; static AccountKey accountKey; @BeforeClass @@ -1634,6 +1635,33 @@ public static void init() { "0x4fe2a845d92ff48abca3e1d59637fab5f4a4e3172d91772d9bfce60760edc506" ) )); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + )) + ); + + accessTxBuilder = new EthereumAccessList.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x7b65b75d204abed71587c9e519a89277766ee1d0") + .setValue("0xa") + .setChainId("0x2") + .setGasPrice("0x19") + .setNonce("0x4d2") + .setGas("0xf4240") + .setInput("0x31323334") + .setAccessList(accessList) + .setSignatures(new SignatureData( + "0x1", + "0xbfc80a874c43b71b67c68fa5927d1443407f31aef4ec6369bbecdb76fc39b0c0", + "0x193e62c1dd63905aee7073958675dcb45d78c716a9a286b54a496e82cb762f26" + )); + } @Test @@ -1645,7 +1673,9 @@ public void isCallValidateSender() throws IOException { Validator spyValidator = Mockito.spy(validator); spyValidator.validateTransaction(txBuilder.build()); + Mockito.verify(spyValidator).validateSender(any()); + spyValidator.validateTransaction(accessTxBuilder.build()); Mockito.verify(spyValidator).validateSender(any()); } @@ -1671,6 +1701,7 @@ public void validateTransaction() throws IOException { Validator validator = new Validator(klay); assertTrue(validator.validateTransaction(txBuilder.build())); assertTrue(validator.validateTransaction(fdTxBuilder.build())); + assertTrue(validator.validateTransaction(accessTxBuilder.build())); } } } \ No newline at end of file From 783f0b19ca029351201e554be1b9868c3d071eee Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Fri, 4 Mar 2022 17:53:47 +0900 Subject: [PATCH 79/89] modified test error --- .../test/java/com/klaytn/caver/validator/ValidatorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java index 48f429418..65fcc9c79 100644 --- a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java +++ b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java @@ -1675,8 +1675,9 @@ public void isCallValidateSender() throws IOException { spyValidator.validateTransaction(txBuilder.build()); Mockito.verify(spyValidator).validateSender(any()); - spyValidator.validateTransaction(accessTxBuilder.build()); - Mockito.verify(spyValidator).validateSender(any()); + Validator spyValidator1 = Mockito.spy(validator); + spyValidator1.validateTransaction(accessTxBuilder.build()); + Mockito.verify(spyValidator1).validateSender(any()); } @Test From 7307f1332fbe016f567b94544646bc80cd5e2528 Mon Sep 17 00:00:00 2001 From: "kale.kim" Date: Sat, 5 Mar 2022 00:00:59 +0900 Subject: [PATCH 80/89] change to version 1.8.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ab83e8525..d4e82cff8 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ apply plugin: 'io.codearte.nexus-staging' allprojects { - version '1.6.4' + version '1.8.0' group 'com.klaytn.caver' description 'caver-java project' From 4b92a48e8dee470bc7144be7274a4450b6759280 Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 5 Mar 2022 04:15:34 +0900 Subject: [PATCH 81/89] Implement EthereumDynamicFee transaction --- build.gradle | 1 + core/build.gradle | 1 + .../caver/methods/response/Transaction.java | 34 +- .../methods/response/TransactionReceipt.java | 32 +- .../caver/transaction/TransactionDecoder.java | 2 + .../caver/transaction/TxPropertyBuilder.java | 8 + .../transaction/type/EthereumDynamicFee.java | 794 ++++++++ .../wrapper/EthereumDynamicFeeWrapper.java | 113 ++ .../wrapper/TransactionWrapper.java | 6 + .../response/TransactionReceiptTest.java | 49 + .../methods/response/TransactionTest.java | 45 + .../transaction/EthereumDynamicFeeTest.java | 1664 +++++++++++++++++ .../src/test/resources/TransactionSample.json | 64 +- 13 files changed, 2800 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java create mode 100644 core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java create mode 100644 core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java diff --git a/build.gradle b/build.gradle index d4e82cff8..c9f0db700 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ buildscript { ext.androidxJunitVersion = '1.1.2' ext.androidxTestRunnerVersion = '1.3.0' ext.androidxTestRulesVersion = '1.3.0' + ext.jacksonDataBindVersion = '2.13.1' } plugins { diff --git a/core/build.gradle b/core/build.gradle index 8a23099f8..e42ab9a3f 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,5 +8,6 @@ dependencies { } compile "com.github.ipfs:java-ipfs-http-client:$ipfsVersion" + implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDataBindVersion" } diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java index 64f026e52..32fe24e65 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java @@ -16,6 +16,7 @@ package com.klaytn.caver.methods.response; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -87,6 +88,16 @@ public static class TransactionData { */ private String gasPrice; + /** + * Max priority fee per gas in peb. + */ + private String maxPriorityFeePerGas; + + /** + * Max fee per gas in peb. + */ + private String maxFeePerGas; + /** * Hash of the transaction. */ @@ -152,6 +163,7 @@ public static class TransactionData { /** * Chain ID. */ + @JsonAlias({"chainId", "chainID"}) private String chainID; /** @@ -161,7 +173,7 @@ public static class TransactionData { public TransactionData() {} - public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value, String chainID, AccessList accesslist) { + public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String maxPriorityFeePerGas, String maxFeePerGas, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value, String chainID, AccessList accesslist) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -171,6 +183,8 @@ public TransactionData(String blockHash, String blockNumber, String codeFormat, this.from = from; this.gas = gas; this.gasPrice = gasPrice; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; this.hash = hash; this.humanReadable = humanReadable; this.key = key; @@ -260,6 +274,22 @@ public void setGasPrice(String gasPrice) { this.gasPrice = gasPrice; } + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + public void setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + } + public String getHash() { return hash; } @@ -429,6 +459,8 @@ public AbstractTransaction convertToCaverTransaction(Klay klay) { return FeeDelegatedChainDataAnchoringWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getInput()); case TxTypeEthereumAccessList: return EthereumAccessList.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue(), this.getAccessList()); + case TxTypeEthereumDynamicFee: + return EthereumDynamicFee.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getMaxPriorityFeePerGas(), this.getMaxFeePerGas(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue(), this.getAccessList()); default: throw new RuntimeException("Invalid transaction type : Cannot create a transaction instance that has Tx type :" + this.getType()); } diff --git a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java index 808e773d9..173bd7dab 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java @@ -16,6 +16,7 @@ package com.klaytn.caver.methods.response; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -133,6 +134,16 @@ public static class TransactionReceiptData { */ private String gasPrice; + /** + * Max priority fee per gas in peb. + */ + private String maxPriorityFeePerGas; + + /** + * Max fee per gas in peb. + */ + private String maxFeePerGas; + /** * The amount of gas used by this specific transaction alone. */ @@ -223,6 +234,7 @@ public static class TransactionReceiptData { /** * Chain ID. */ + @JsonAlias({"chainId", "chainID"}) private String chainID; /** @@ -233,7 +245,7 @@ public static class TransactionReceiptData { public TransactionReceiptData() { } - public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value, String chainID, AccessList accessList) { + public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String maxPriorityFeePerGas, String maxFeePerGas, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value, String chainID, AccessList accessList) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -244,6 +256,8 @@ public TransactionReceiptData(String blockHash, String blockNumber, String codeF this.from = from; this.gas = gas; this.gasPrice = gasPrice; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; this.gasUsed = gasUsed; this.humanReadable = humanReadable; this.key = key; @@ -346,6 +360,22 @@ public void setGasPrice(String gasPrice) { this.gasPrice = gasPrice; } + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + public void setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + } + public String getGasUsed() { return gasUsed; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java index 30c244b51..fbb37f70c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java @@ -72,6 +72,8 @@ public static AbstractTransaction decode(String rlpEncoded) { return FeeDelegatedSmartContractDeployWithRatio.decode(rlpBytes); } else if ((rlpBytes[0] << 8 | rlpBytes[1]) == TransactionType.TxTypeEthereumAccessList.getType()) { return EthereumAccessList.decode(rlpBytes); + } else if ((rlpBytes[0] << 8 | rlpBytes[1]) == TransactionType.TxTypeEthereumDynamicFee.getType()) { + return EthereumDynamicFee.decode(rlpBytes); } else { return LegacyTransaction.decode(rlpBytes); diff --git a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java index b3b0fdb7e..3e25f1247 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java @@ -38,6 +38,14 @@ public static EthereumAccessList.Builder ethereumAccessList() { return new EthereumAccessList.Builder(); } + /** + * Creates a Builder of EthereumDynamicFee + * @return EthereumDynamicFee.Builder + */ + public static EthereumDynamicFee.Builder ethereumDynamicFee() { + return new EthereumDynamicFee.Builder(); + } + /** * Creates a Builder of ValueTransfer * @return ValueTransfer.Builder diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java new file mode 100644 index 000000000..d3d4ae49f --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -0,0 +1,794 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.type; + +import com.klaytn.caver.account.AccountKeyRoleBased; +import com.klaytn.caver.methods.response.BlockHeader; +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TransactionHelper; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.KeyringFactory; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.web3j.crypto.Hash; +import org.web3j.protocol.core.DefaultBlockParameterName; +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Represents an ethereum access list transaction. + */ +public class EthereumDynamicFee extends AbstractTransaction { + /** + * The account address that will receive the transferred value. + */ + String to = "0x"; + + /** + * Data attached to the transaction, used for transaction execution. + */ + String input = "0x"; + + /** + * The amount of KLAY in peb to be transferred. + */ + String value = "0x00"; + + /** + * Access list is an EIP-2930 access list. + */ + AccessList accessList; + + /** + * A max priority fee per gas. + */ + String maxPriorityFeePerGas = "0x"; + + /** + * A max fee per gas. + */ + String maxFeePerGas = "0x"; + + /** + * EthereumDynamicFee Builder class + */ + public static class Builder extends AbstractTransaction.Builder { + private String to = "0x"; + private String value = "0x0"; + private String input = "0x"; + private AccessList accessList = new AccessList(); + String maxPriorityFeePerGas = "0x"; + String maxFeePerGas = "0x"; + + public Builder() { + super(TransactionType.TxTypeEthereumDynamicFee.toString()); + } + + public Builder setValue(String value) { + this.value = value; + return this; + } + + public Builder setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + return this; + } + + public Builder setInput(String input) { + this.input = input; + return this; + } + + public Builder setTo(String to) { + this.to = to; + return this; + } + + public Builder setAccessList(AccessList accessList) { + this.accessList = accessList; + return this; + } + + public Builder setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + return this; + } + + public Builder setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { + setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); + return this; + } + + public Builder setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + return this; + } + + public Builder setMaxFeePerGas(BigInteger maxFeePerGas) { + setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); + return this; + } + + public EthereumDynamicFee build() { + return new EthereumDynamicFee(this); + } + } + + /** + * Creates a EthereumDynamicFee instance. + * + * @param builder EthereumDynamicFee.Builder instance. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee create(EthereumDynamicFee.Builder builder) { + return new EthereumDynamicFee(builder); + } + + + /** + * Create a EthereumDynamicFee instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max priority fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee create(Klay klaytnCall, String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return new EthereumDynamicFee(klaytnCall, from, nonce, gas, maxPriorityFeePerGas, maxFeePerGas, chainId, signatures, to, input, value, accessList); + } + + /** + * Creates a EthereumDynamicFee instance. + * + * @param builder EthereumDynamicFee.Builder instance. + */ + public EthereumDynamicFee(EthereumDynamicFee.Builder builder) { + super(builder); + + setTo(builder.to); + setValue(builder.value); + setInput(builder.input); + setAccessList(builder.accessList); + setMaxPriorityFeePerGas(builder.maxPriorityFeePerGas); + setMaxFeePerGas(builder.maxFeePerGas); + } + + + /** + * Create a EthereumDynamicFee instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max priority fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + */ + public EthereumDynamicFee(Klay klaytnCall, String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + super( + klaytnCall, + TransactionType.TxTypeEthereumDynamicFee.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setTo(to); + setValue(value); + setInput(input); + setAccessList(accessList); + setMaxPriorityFeePerGas(maxPriorityFeePerGas); + setMaxFeePerGas(maxFeePerGas); + } + + /** + * Getter function for maxPriorityFeePerGas + * @return String + */ + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + if(maxPriorityFeePerGas == null || maxPriorityFeePerGas.isEmpty() || maxPriorityFeePerGas.equals("0x")) { + maxPriorityFeePerGas = "0x"; + } + + if(!maxPriorityFeePerGas.equals("0x") && !Utils.isNumber(maxPriorityFeePerGas)) { + throw new IllegalArgumentException("Invalid maxPriorityFeePerGas. : " + maxPriorityFeePerGas); + } + + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { + setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); + } + + /** + * Getter function for maxFeePerGas. + * @return String + */ + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + /** + * Setter function for maxFeePerGas. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(String maxFeePerGas) { + if(maxFeePerGas == null || maxFeePerGas.isEmpty() || maxFeePerGas.equals("0x")) { + maxFeePerGas = "0x"; + } + + if(!maxFeePerGas.equals("0x") && !Utils.isNumber(maxFeePerGas)) { + throw new IllegalArgumentException("Invalid maxFeePerGas. : " + maxFeePerGas); + } + + this.maxFeePerGas = maxFeePerGas; + } + + /** + * Setter function for gas price. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(BigInteger maxFeePerGas) { + setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); + } + + + @Override + public String getRLPEncoding() { + // TransactionPayload = 0x7801 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxPriorityFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + SignatureData signatureData = this.getSignatures().get(0); + rlpTypeList.addAll(signatureData.toRlpList().getValues()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = Numeric.toBytesPadded(BigInteger.valueOf(TransactionType.TxTypeEthereumDynamicFee.getType()), 2); + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + @Override + public String getCommonRLPEncodingForSignature() { + return getRLPEncodingForSignature(); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * + * @return String + */ + @Override + public String getRLPEncodingForSignature() { // SigRLP = 0x01 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]) + + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxPriorityFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = new byte[]{(byte) TransactionType.TxTypeEthereumDynamicFee.getType()}; + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Check equals txObj passed parameter and Current instance. + * + * @param obj The AbstractTransaction Object to compare + * @param checkSig Check whether signatures field is equal. + * @return boolean + */ + @Override + public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { + if (!super.compareTxField(obj, checkSig)) return false; + if (!(obj instanceof EthereumDynamicFee)) return false; + EthereumDynamicFee txObj = (EthereumDynamicFee) obj; + + if (!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; + if (!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getInput().equals(txObj.getInput())) return false; + if (!this.getAccessList().equals(txObj.getAccessList())) return false; + if(Numeric.toBigInt(this.getMaxPriorityFeePerGas()).compareTo(Numeric.toBigInt(txObj.getMaxPriorityFeePerGas())) != 0) return false; + if(Numeric.toBigInt(this.getMaxFeePerGas()).compareTo(Numeric.toBigInt(txObj.getMaxFeePerGas())) != 0) return false; + + return true; + } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + EthereumDynamicFee txObj = (EthereumDynamicFee) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getMaxPriorityFeePerGas().equals("0x")) this.setMaxPriorityFeePerGas(txObj.getMaxPriorityFeePerGas()); + if(this.getMaxFeePerGas().equals("0x")) this.setMaxFeePerGas(txObj.getMaxFeePerGas()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.getMaxPriorityFeePerGas().equals("0x")) { + this.setMaxPriorityFeePerGas(this.getKlaytnCall().getMaxPriorityFeePerGas().send().getResult()); + } + if(this.getMaxFeePerGas().equals("0x")) { + BlockHeader blockHeader = this.getKlaytnCall().getHeader(DefaultBlockParameterName.LATEST).send(); + BigInteger baseFee = Numeric.toBigInt(blockHeader.getResult().getBaseFeePerGas()); + BigInteger maxPriorityFeePerGas = Numeric.toBigInt(this.getMaxPriorityFeePerGas()); + this.setMaxFeePerGas(baseFee.multiply(BigInteger.valueOf(2)).add(maxPriorityFeePerGas)); + } + if(this.getMaxPriorityFeePerGas().equals("0x") || this.getMaxFeePerGas().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (maxPriorityFeePerGas, maxFeePerGas). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getMaxPriorityFeePerGas() == null || this.getMaxPriorityFeePerGas().isEmpty() || this.getMaxPriorityFeePerGas().equals("0x")) { + throw new RuntimeException("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + } + if(this.getMaxFeePerGas() == null || this.getMaxFeePerGas().isEmpty() || this.getMaxFeePerGas().equals("0x")) { + throw new RuntimeException("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee string. + * + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee decode(String rlpEncoded) { + return decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee byte array. + * + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee decode(byte[] rlpEncoded) { + // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + try { + if ((rlpEncoded[0] << 8 | rlpEncoded[1]) != TransactionType.TxTypeEthereumDynamicFee.getType()) { + throw new IllegalArgumentException("Invalid RLP-encoded tag - " + TransactionType.TxTypeEthereumDynamicFee.toString()); + } + byte[] detachedType = Arrays.copyOfRange(rlpEncoded, 2, rlpEncoded.length); + RlpList rlpList = RlpDecoder.decode(detachedType); + List values = ((RlpList) rlpList.getValues().get(0)).getValues(); + + BigInteger chainId = ((RlpString) values.get(0)).asPositiveBigInteger(); + BigInteger nonce = ((RlpString) values.get(1)).asPositiveBigInteger(); + BigInteger maxPriorityFeePerGas = ((RlpString) values.get(2)).asPositiveBigInteger(); + BigInteger maxFeePerGas = ((RlpString) values.get(3)).asPositiveBigInteger(); + BigInteger gas = ((RlpString) values.get(4)).asPositiveBigInteger(); + String to = ((RlpString) values.get(5)).asString(); + BigInteger value = ((RlpString) values.get(6)).asPositiveBigInteger(); + String input = ((RlpString) values.get(7)).asString(); + + AccessList accessList = AccessList.decode((RlpList) values.get(8)); + + EthereumDynamicFee ethereumAccessList = new EthereumDynamicFee.Builder() + .setFrom(null) + .setInput(input) + .setValue(value) + .setChainId(chainId) + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setTo(to) + .setAccessList(accessList) + .build(); + + byte[] v = ((RlpString) values.get(9)).getBytes(); + byte[] r = ((RlpString) values.get(10)).getBytes(); + byte[] s = ((RlpString) values.get(11)).getBytes(); + SignatureData signatureData = new SignatureData(v, r, s); + + ethereumAccessList.appendSignatures(signatureData); + return ethereumAccessList; + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + /** + * Appends signatures array to transaction. + * EthereumDynamicFee transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData SignatureData instance contains ECDSA signature data + */ + @Override + public void appendSignatures(SignatureData signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures().get(0))) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + if (!Utils.isEmptySig(signatureData)) { + int v = Integer.decode(signatureData.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + }; + super.appendSignatures(signatureData); + } + + /** + * Appends signatures array to transaction. + * EthereumDynamicFee transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData List of SignatureData contains ECDSA signature data + */ + @Override + public void appendSignatures(List signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + if(signatureData.size() != 1) { + throw new RuntimeException("Signatures are too long " + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + SignatureData signature = signatureData.get(0); + if (!Utils.isEmptySig(signature)) { + int v = Integer.decode(signature.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + }; + + super.appendSignatures(signatureData); + } + + /** + * Returns a hash string of transaction + * @return String + */ + @Override + public String getTransactionHash() { + // TxHashRLP = 0x01 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + String rlpEncoded = this.getRLPEncoding(); + byte[] rlpEncodedBytes = Numeric.hexStringToByteArray(rlpEncoded); + byte[] detachedType = Arrays.copyOfRange(rlpEncodedBytes, 1, rlpEncodedBytes.length); + return Hash.sha3(Numeric.toHexString(detachedType)); + } + + /** + * Signs to the transaction with a single private key. + * It sets Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyString The private key string. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * @param keyring The Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + List sigList = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + this.appendSignatures(sigList); + + return this; + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a single private key. + * @param keyString The private key string + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString, Function signer) throws IOException { + + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, signer); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * It sets signer to TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOException { + return this.sign(keyring, index, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + SignatureData signatureData = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex(), index); + + this.appendSignatures(signatureData); + + return this; + } + + /** + * Getter function for to + * + * @return String + */ + public String getTo() { + return to; + } + + /** + * Setter function for to + * + * @param to The account address that will receive the transferred value. + */ + public void setTo(String to) { + // "to" field in EthereumDynamicFee allows null + if (to == null || to.isEmpty()) { + to = "0x"; + } + + if (!to.equals("0x") && !Utils.isAddress(to)) { + throw new IllegalArgumentException("Invalid address. : " + to); + } + this.to = to; + } + + /** + * Getter function for input + * + * @return String + */ + public String getInput() { + return input; + } + + /** + * Setter function for input + * + * @param input Data attached to the transaction. + */ + public void setInput(String input) { + if (input == null) { + throw new IllegalArgumentException("input is missing"); + } + + if (!Utils.isHex(input)) { + throw new IllegalArgumentException("Invalid input : " + input); + } + this.input = Numeric.prependHexPrefix(input); + } + + + /** + * Getter function for value + * + * @return String + */ + public String getValue() { + return value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(String value) { + if (value == null) { + throw new IllegalArgumentException("value is missing"); + } + + if (!Utils.isNumber(value)) { + throw new IllegalArgumentException("Invalid value : " + value); + } + this.value = value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + } + + /** + * Getter function for accessList + * + * @return accessList Access list is an EIP-2930 access list. + */ + public AccessList getAccessList() { + return accessList; + } + + /** + * Setter function for accessList + * + * @param accessList Access list is an EIP-2930 access list. + */ + public void setAccessList(AccessList accessList) { + if (accessList == null) { + accessList = new AccessList(); + } + this.accessList = accessList; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java new file mode 100644 index 000000000..864608bef --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java @@ -0,0 +1,113 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.transaction.type.wrapper; + +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.type.EthereumDynamicFee; +import com.klaytn.caver.wallet.keyring.SignatureData; + +import java.util.List; + +/** + * Represents a EthereumDynamicFeeWrapper + * 1. This class wraps all of static methods of EthereumDynamicFee + * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + */ +public class EthereumDynamicFeeWrapper { + /** + * Klay RPC instance + */ + private Klay klaytnCall; + + /** + * Creates a EthereumDynamicFeeWrapper instance. + * @param klaytnCall Klay RPC instance + */ + public EthereumDynamicFeeWrapper(Klay klaytnCall) { + this.klaytnCall = klaytnCall; + } + + /** + * Creates a EthereumDynamicFee instance derived from a RLP-encoded EthereumDynamicFee string. + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(String rlpEncoded) { + EthereumDynamicFee legacyTransaction = EthereumDynamicFee.decode(rlpEncoded); + legacyTransaction.setKlaytnCall(this.klaytnCall); + return legacyTransaction; + } + + /** + * Creates a EthereumDynamicFee instance derived from a RLP-encoded EthereumDynamicFee byte array. + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(byte[] rlpEncoded) { + EthereumDynamicFee legacyTransaction = EthereumDynamicFee.decode(rlpEncoded); + legacyTransaction.setKlaytnCall(this.klaytnCall); + return legacyTransaction; + } + + /** + * Creates a EthereumDynamicFee instance using EthereumDynamicFee.Builder + * @param builder EthereumDynamicFee.Builder + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(EthereumDynamicFee.Builder builder) { + builder.setKlaytnCall(this.klaytnCall); + return EthereumDynamicFee.create(builder); + } + + /** + * Create a EthereumDynamicFee instance. + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max prirotiy fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return EthereumDynamicFee.create(this.klaytnCall, from, nonce, gas, maxPriorityFeePerGas, maxFeePerGas, chainId, signatures, to, input, value, accessList); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee string. + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public EthereumDynamicFee decode(String rlpEncoded) { + return EthereumDynamicFee.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee byte array. + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee decode(byte[] rlpEncoded) { + return EthereumDynamicFee.decode(rlpEncoded); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index f8b6928f6..7d7c6a556 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -44,6 +44,11 @@ public class TransactionWrapper { */ public EthereumAccessListWrapper ethereumAccessList; + /** + * EthereumDynamicFeeWrapper instance + */ + public EthereumDynamicFeeWrapper ethereumDynamicFee; + /** * ValueTransferWrapper instance */ @@ -163,6 +168,7 @@ public TransactionWrapper(Klay klaytnCall) { this.legacyTransaction = new LegacyTransactionWrapper(klaytnCall); this.ethereumAccessList = new EthereumAccessListWrapper(klaytnCall); + this.ethereumDynamicFee = new EthereumDynamicFeeWrapper(klaytnCall); this.valueTransfer = new ValueTransferWrapper(klaytnCall); this.feeDelegatedValueTransfer = new FeeDelegatedValueTransferWrapper(klaytnCall); diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java index 7812d590c..d57dd8ee4 100644 --- a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java @@ -62,4 +62,53 @@ public void deserializeTest() throws IOException { System.out.println(objectToString(transactionReceiptData)); } } + + public static class ethereumDynamicFeeTest { + @Test + public void deserializeTest() throws IOException { + String ethereumDynamicFeeReceiptJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0\",\n" + + " \"blockNumber\": \"0xabc1\",\n" + + " \"chainId\": \"0x7e3\",\n" + + " \"contractAddress\": null,\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0x7a120\",\n" + + " \"gasUsed\": \"0x6a40\",\n" + + " \"input\": \"0x\",\n" + + " \"logs\": [],\n" + + " \"logsBloom\": \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"maxFeePerGas\": \"0x5d21dba00\",\n" + + " \"maxPriorityFeePerGas\": \"0x5d21dba00\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"V\": \"0x0\",\n" + + " \"R\": \"0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923\",\n" + + " \"S\": \"0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb\"\n" + + " }\n" + + " ],\n" + + " \"status\": \"0x1\",\n" + + " \"to\": \"0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499\",\n" + + " \"transactionHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"transactionIndex\": \"0x0\",\n" + + " \"type\": \"TxTypeEthereumDynamicFee\",\n" + + " \"typeInt\": 30722,\n" + + " \"value\": \"0x1\"\n" + + "}"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumDynamicFeeReceiptJson); + TransactionReceipt.TransactionReceiptData transactionReceiptData = objectMapper.readValue(reader, TransactionReceipt.TransactionReceiptData.class); + System.out.println(objectToString(transactionReceiptData)); + } + } } diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java index 69268eed5..f709e3280 100644 --- a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java @@ -61,4 +61,49 @@ public void deserializeTest() throws IOException { System.out.println(objectToString(transactionData)); } } + + public static class ethereumDynamicFeeTest { + + @Test + public void deserializeTest() throws IOException { + String ethereumDynamicFee = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0\",\n" + + " \"blockNumber\": \"0xabc1\",\n" + + " \"chainId\": \"0x7e3\",\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0x7a120\",\n" + + " \"hash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"input\": \"0x\",\n" + + " \"maxFeePerGas\": \"0x5d21dba00\",\n" + + " \"maxPriorityFeePerGas\": \"0x5d21dba00\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"V\": \"0x0\",\n" + + " \"R\": \"0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923\",\n" + + " \"S\": \"0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb\"\n" + + " }\n" + + " ],\n" + + " \"to\": \"0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499\",\n" + + " \"transactionIndex\": \"0x0\",\n" + + " \"type\": \"TxTypeEthereumDynamicFee\",\n" + + " \"typeInt\": 30722,\n" + + " \"value\": \"0x1\"\n" + + " }"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumDynamicFee); + Transaction.TransactionData transactionData = objectMapper.readValue(reader, Transaction.TransactionData.class); + System.out.println(objectToString(transactionData)); + } + } } diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java new file mode 100644 index 000000000..b05b6191c --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java @@ -0,0 +1,1664 @@ +/* + * Copyright 2022 The caver-java Authors + * + * 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 com.klaytn.caver.common.transaction; + +import com.klaytn.caver.Caver; +import com.klaytn.caver.abi.datatypes.Array; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TxPropertyBuilder; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.transaction.type.EthereumDynamicFee; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.junit.*; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.crypto.Hash; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(Enclosed.class) +public class EthereumDynamicFeeTest { + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String nonce = "0x4D2"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + String chainID = "0x2710"; + String input = "0x31323334"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void createInstance() throws IOException { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x") + .setGas(gas) + .setMaxFeePerGas("0x") + .setMaxPriorityFeePerGas("0x") + .setChainId("0x") + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + + assertNotNull(ethereumDynamicFee); + + ethereumDynamicFee.fillTransaction(); + assertNotNull(ethereumDynamicFee.getNonce()); + assertNotNull(ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertNotNull(ethereumDynamicFee.getMaxFeePerGas()); + assertNotNull(ethereumDynamicFee.getChainId()); + assertEquals(accessList, ethereumDynamicFee.getAccessList()); + assertEquals(TransactionType.TxTypeEthereumDynamicFee.toString(), ethereumDynamicFee.getType()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + String value = "invalid"; + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String to = "invalid"; + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + } + + public static class createInstanceBuilder { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + String nonce = "0x4D2"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + String chainID = "0x2710"; + String input = "0x31323334"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void BuilderTest() { + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertNotNull(ethereumDynamicFee); + assertEquals(TransactionType.TxTypeEthereumDynamicFee.toString(), ethereumDynamicFee.getType()); + } + + @Test + public void BuilderTestWithBigInteger() { + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(Numeric.toBigInt(maxPriorityFeePerGas)) + .setMaxFeePerGas(Numeric.toBigInt(maxFeePerGas)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertEquals(gas, ethereumDynamicFee.getGas()); + assertEquals(maxPriorityFeePerGas, ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertEquals(maxFeePerGas, ethereumDynamicFee.getMaxFeePerGas()); + assertEquals(chainID, ethereumDynamicFee.getChainId()); + assertEquals(value, ethereumDynamicFee.getValue()); + assertEquals(accessList, ethereumDynamicFee.getAccessList()); + } + + @Test + public void BuilderTestRPC() throws IOException { + String gas = "0x0f4240"; + String to = "7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0x0a"; + + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setKlaytnCall(caver.rpc.getKlay()) + .setGas(gas) + .setInput(input) + .setValue(value) + .setFrom(keyring.getAddress()) // For Test. It automatically filled when executed EthereumDynamicFee.signWithKey or signWithKeysTest. + .setTo(to) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.fillTransaction(); + assertNotNull(ethereumDynamicFee.getNonce()); + assertNotNull(ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertNotNull(ethereumDynamicFee.getMaxFeePerGas()); + assertNotNull(ethereumDynamicFee.getChainId()); + assertNotNull(ethereumDynamicFee.getAccessList()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("0x") + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_invalid_value2() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("invalid") + .build(); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String gas = "0xf4240"; + String to = "invalid"; + String input = "0x31323334"; + String value = "0xa"; + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + } + + public static class getRLPEncodingAndDecodingTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void getRLPEncodingAndDecoding() { + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + // 0 y-parity + String expectedRLP = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x25") + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo("0x1fc92c23f71a7de4cdb4394a37fc636986a0f484") + .setValue(value) + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(accessList) + .setSignatures(signatureData) + ); + assertEquals(expectedRLP, ethereumDynamicFee.getRLPEncoding()); + // decoding + EthereumDynamicFee decodedEthereumDynamicFee = (EthereumDynamicFee) caver.transaction.decode(expectedRLP); + assertTrue(ethereumDynamicFee.compareTxField(decodedEthereumDynamicFee, true)); + + // 1 y-parity + expectedRLP = "0x7802f9010f822710288505d21dba008505d21dba00829c40947988508e9236a5b796ddbb6ac40864777a414f5f01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994d9d6bd9e2186233d9441bde052504b926f2e0bb2f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a054d6ea6f359a7d3546199ac93dca216918b45647a45b6f32be58f33735a696b7a07179ffc15f5c6b4b08efc4f7306548c435529edc2e5b8243d1193f52085dbc65"; + ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x28") + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo("0x7988508e9236a5b796ddbb6ac40864777a414f5f") + .setValue(value) + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create( + Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0xd9d6bd9e2186233d9441bde052504b926f2e0bb2", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ) + )) + .setSignatures(new SignatureData( + "0x01", + "0x54d6ea6f359a7d3546199ac93dca216918b45647a45b6f32be58f33735a696b7", + "0x7179ffc15f5c6b4b08efc4f7306548c435529edc2e5b8243d1193f52085dbc65") + ) + ); + assertEquals(expectedRLP, ethereumDynamicFee.getRLPEncoding()); + // decoding + decodedEthereumDynamicFee = (EthereumDynamicFee) caver.transaction.decode(expectedRLP); + assertTrue(ethereumDynamicFee.compareTxField(decodedEthereumDynamicFee, true)); + } + + @Test + public void throwException_NoNonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x1"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoMaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoMaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + } + + public static class getTransactionHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void getTransactionHash() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0xf3a22f2ce973c9aa250d0dc64044e3dec9592b787f5abe3f557f3e81b99e3ca1"; + String txHash = ethereumDynamicFee.getTransactionHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.getTransactionHash(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getRawTransaction(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getRawTransaction(); + } + + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getTransactionHash(); + } + } + + + public static class getSenderTxHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void getSenderTxHash() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0xf3a22f2ce973c9aa250d0dc64044e3dec9592b787f5abe3f557f3e81b99e3ca1"; + String txHash = ethereumDynamicFee.getSenderTxHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + } + + public static class combineSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + @Test + public void combineSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumDynamicFee.combineSignedRawTransactions(rlpList); + assertEquals(rlpEncoded, combined); + EthereumDynamicFee decoded = (EthereumDynamicFee) caver.transaction.decode(combined); + Assert.assertTrue(decoded.getSignatures().equals(Arrays.asList(signatureData))); + } + + @Test + public void combineSignature_EmptySig() { + SignatureData emptySig = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySig) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumDynamicFee.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + EthereumDynamicFee decoded = (EthereumDynamicFee) caver.transaction.decode(combined); + Assert.assertTrue(decoded.getSignatures().equals(Arrays.asList(signatureData))); + } + + @Test + public void throwException_existSignature() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumDynamicFee.combineSignedRawTransactions(rlpList); + } + + @Test + public void throwException_differentField() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Transactions containing different information cannot be combined."); + + // rlpEncoded is a rlp encoded string of EthereumDynamicFee transaction containing above fields. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + // All fields are same with above rlpEncoded EthereumDynamicFee transaction except maxFeePerGas field. + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas("0x1") + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumDynamicFee.combineSignedRawTransactions(rlpList); + } + } + + public static class appendSignaturesTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void appendSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void throwException_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )); + } + + + @Test + public void throwException_setSignature_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + } + + + @Test + public void appendSignatureWithEmptySig() { + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void appendSignatureList() { + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(list); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void appendSignatureList_EmptySig() { + List list = new ArrayList<>(); + list.add(signatureData); + + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumDynamicFee.appendSignatures(list); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void throwException_appendData_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + } + + @Test + public void throwException_appendList_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(list) + ); + + ethereumDynamicFee.appendSignatures(list); + } + + @Test + public void throwException_tooLongSignatures() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures are too long " + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(list); + } + } + + public static class getRLPEncodingForSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void getRLPEncodingForSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + String expected = "0x02f8cc822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007"; + String rlpEncodedSign = ethereumDynamicFee.getRLPEncodingForSignature(); + + assertEquals(expected, rlpEncodedSign); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_ChainID() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + } + + public static class signWithKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String nonce = "0x25"; + static String gas = "0x9c40"; + static String maxPriorityFeePerGas = "0x5d21dba00"; + static String maxFeePerGas = "0x5d21dba00"; + static String chainID = "0x2710"; + static String value = "0x1"; + static String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + static String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + static AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + public static EthereumDynamicFee createEthereumDynamicFee() { + return caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + static String expectedRlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a02e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208fa076cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKey_Keyring() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, 0, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoIndex() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoSigner() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, 0); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_Only() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_KeyString_NoIndex() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("TxTypeEthereumDynamicFee cannot be signed with a decoupled keyring."); + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + ethereumDynamicFee.sign(deCoupledKeyring); + } + + @Test + public void throwException_notEqualAddress() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The from address of the transaction is different with the address of the keyring to use"); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(Numeric.toBigInt(gas)) + .setMaxPriorityFeePerGas(Numeric.toBigInt(maxPriorityFeePerGas)) + .setMaxFeePerGas(Numeric.toBigInt(maxFeePerGas)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setFrom("0x7b65b75d204abed71587c9e519a89277766aaaa1") + .setTo(to) + .setAccessList(accessList) + ); + + ethereumDynamicFee.sign(coupledKeyring); + } + } + + public static class signWithKeysTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String nonce = "0x25"; + static String gas = "0x9c40"; + static String maxPriorityFeePerGas = "0x5d21dba00"; + static String maxFeePerGas = "0x5d21dba00"; + static String chainID = "0x2710"; + static String value = "0x1"; + static String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + static String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + static AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + public static EthereumDynamicFee createEthereumDynamicFee() { + return caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + static String expectedRlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a02e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208fa076cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4"; + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKeys_KeyString() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKeys_KeyString_KlaytnWalletKeyFormat() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.createFromPrivateKey(privateKey).getAddress(); + AbstractTransaction tx = ethereumDynamicFee.sign(klaytnKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.generate().getAddress(); + ethereumDynamicFee.sign(klaytnKey); + } + + @Test + public void throwException_multipleKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + String[] privateKeyArr = { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }; + + AbstractKeyring keyring = caver.wallet.keyring.createWithMultipleKey( + caver.wallet.keyring.generate().getAddress(), + privateKeyArr + ); + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + ethereumDynamicFee.sign(keyring); + } + + @Test + public void throwException_roleBasedKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + String[][] privateKeyArr = { + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + } + }; + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + + AbstractKeyring keyring = caver.wallet.keyring.createWithRoleBasedKey( + caver.wallet.keyring.generate().getAddress(), + Arrays.asList(privateKeyArr) + ); + ethereumDynamicFee.sign(keyring); + } + } + + public static class recoverPublicKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void recoverPublicKey() { + + String expectedPublicKey = "0x3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3"; + SignatureData signatureData = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + EthereumDynamicFee tx = new EthereumDynamicFee.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x1fc92c23f71a7de4cdb4394a37fc636986a0f484") + .setValue("0x1") + .setChainId("0x2710") + .setMaxPriorityFeePerGas("0x5d21dba00") + .setMaxFeePerGas("0x5d21dba00") + .setNonce("0x25") + .setGas("0x9c40") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(accessList) + .setSignatures(signatureData) + .build(); + + List publicKeys = tx.recoverPublicKeys(); + assertEquals(expectedPublicKey, publicKeys.get(0)); + } + } +} diff --git a/core/src/test/resources/TransactionSample.json b/core/src/test/resources/TransactionSample.json index 1ab5bfe70..fa084bd9c 100644 --- a/core/src/test/resources/TransactionSample.json +++ b/core/src/test/resources/TransactionSample.json @@ -622,31 +622,73 @@ { "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", "storageKeys": [ - "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f" + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" + ] + }, + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" ] } ], - "blockHash": "0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e", - "blockNumber": "0x5b", + "blockHash": "0x62c20ccb0243406c85d22fabc83f7cc43c7f5a3e954af4fd70a4e87c38f8b24f", + "blockNumber": "0xb01a", "chainID": "0x7e3", "from": "0xca7a99380131e6c76cfa622396347107aeedca2d", - "gas": "0xf423f", + "gas": "0x1869f", "gasPrice": "0x5d21dba00", - "hash": "0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71", + "hash": "0x8c2972b9a5a63a906158f82ea89cde83ffe85ffca62c9b1b976fd0509e8b6674", "input": "0x", - "nonce": "0x0", - "senderTxHash": "0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71", + "nonce": "0x2", + "senderTxHash": "0x8c2972b9a5a63a906158f82ea89cde83ffe85ffca62c9b1b976fd0509e8b6674", "signatures": [ { - "R": "0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75", - "S": "0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42", - "V": "0x1" + "V": "0x0", + "R": "0x49474db0a0b14b71c54ec00da7575d8c76045ec5bc6873d499c37015f73bfd30", + "S": "0x17fa9232414eb09a6dd1822e0fb4c77acb58bd524f0763863d99ea9c089761e6" } ], - "to": "0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee", + "to": "0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499", "transactionIndex": "0x0", "type": "TxTypeEthereumAccessList", "typeInt": 30721, "value": "0x1" + }, + "ethereumDynamicFee": { + "accessList": [ + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" + ] + } + ], + "blockHash": "0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0", + "blockNumber": "0xabc1", + "chainId": "0x7e3", + "from": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "gas": "0x7a120", + "hash": "0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19", + "input": "0x", + "maxFeePerGas": "0x5d21dba00", + "maxPriorityFeePerGas": "0x5d21dba00", + "nonce": "0x0", + "senderTxHash": "0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19", + "signatures": [ + { + "V": "0x0", + "R": "0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923", + "S": "0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb" + } + ], + "to": "0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499", + "transactionIndex": "0x0", + "type": "TxTypeEthereumDynamicFee", + "typeInt": 30722, + "value": "0x1" } } \ No newline at end of file From 58ee6a677a03a991c5199fa351934f7d237dc5fd Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 5 Mar 2022 11:02:10 +0900 Subject: [PATCH 82/89] Includes chainId as JsonProperty and ignore it by child classes when it needs to be --- .../java/com/klaytn/caver/transaction/AbstractTransaction.java | 1 - .../java/com/klaytn/caver/transaction/type/AccountUpdate.java | 2 ++ .../src/main/java/com/klaytn/caver/transaction/type/Cancel.java | 2 ++ .../com/klaytn/caver/transaction/type/ChainDataAnchoring.java | 2 ++ .../caver/transaction/type/FeeDelegatedAccountUpdate.java | 2 ++ .../transaction/type/FeeDelegatedAccountUpdateWithRatio.java | 2 ++ .../com/klaytn/caver/transaction/type/FeeDelegatedCancel.java | 2 ++ .../caver/transaction/type/FeeDelegatedCancelWithRatio.java | 2 ++ .../caver/transaction/type/FeeDelegatedChainDataAnchoring.java | 2 ++ .../type/FeeDelegatedChainDataAnchoringWithRatio.java | 2 ++ .../caver/transaction/type/FeeDelegatedSmartContractDeploy.java | 2 ++ .../type/FeeDelegatedSmartContractDeployWithRatio.java | 2 ++ .../transaction/type/FeeDelegatedSmartContractExecution.java | 2 ++ .../type/FeeDelegatedSmartContractExecutionWithRatio.java | 2 ++ .../caver/transaction/type/FeeDelegatedValueTransfer.java | 2 ++ .../caver/transaction/type/FeeDelegatedValueTransferMemo.java | 2 ++ .../type/FeeDelegatedValueTransferMemoWithRatio.java | 2 ++ .../transaction/type/FeeDelegatedValueTransferWithRatio.java | 2 ++ .../com/klaytn/caver/transaction/type/LegacyTransaction.java | 2 ++ .../com/klaytn/caver/transaction/type/SmartContractDeploy.java | 2 ++ .../klaytn/caver/transaction/type/SmartContractExecution.java | 2 ++ .../java/com/klaytn/caver/transaction/type/ValueTransfer.java | 2 ++ .../com/klaytn/caver/transaction/type/ValueTransferMemo.java | 2 ++ 23 files changed, 44 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index 5da86bf27..a0d2a187c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -584,7 +584,6 @@ public String getGas() { * Getter function for chain id * @return String */ - @JsonIgnore public String getChainId() { return chainId; } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 20af3935d..73410c34d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractTransaction; @@ -36,6 +37,7 @@ * Represents an account update transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypeaccountupdate to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class AccountUpdate extends AbstractTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index 9feaf8ce1..7b22789fe 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -35,6 +36,7 @@ * Represents a cancel transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypecancel to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class Cancel extends AbstractTransaction { /** * A unit price of gas in peb the sender will pay for a transaction fee. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index afb6ecd0b..0c4b5fa47 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -35,6 +36,7 @@ * Represents a chain data anchoring transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypechaindataanchoring to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ChainDataAnchoring extends AbstractTransaction { /** * Data of the service chain. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index ad1b4ba53..3bd7687d3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; @@ -38,6 +39,7 @@ * Represents a fee delegated account update transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedaccountupdate to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedAccountUpdate extends AbstractFeeDelegatedTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 12ad9eaa7..5e21a1c5e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; @@ -38,6 +39,7 @@ * Represents a fee delegated account update with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedaccountupdatewithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedAccountUpdateWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index ef72cb9a7..9ad883fda 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated cancel transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedcancel to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedCancel extends AbstractFeeDelegatedTransaction { /** * A unit price of gas in peb the sender will pay for a transaction fee. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index 23df30e82..3dd0724b0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated cancel with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedcancelwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedCancelWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * A unit price of gas in peb the sender will pay for a transaction fee. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index f8371bbd5..7521e2cab 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; @@ -38,6 +39,7 @@ * Represents a fee delegated chain data anchoring transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedchaindataanchoring to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedChainDataAnchoring extends AbstractFeeDelegatedTransaction { /** * Data of the service chain. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index 62ed189e0..926a520fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated chain data anchoring with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedchaindataanchoringwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedChainDataAnchoringWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * Data of the service chain. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index 6e8d371c0..e43a8572a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -37,6 +38,7 @@ * Represents a fee delegated smart contract deploy transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractdeploy to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index 5a48f7eca..1f0dd9ace 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -38,6 +39,7 @@ * Represents a fee delegated smart contract deploy with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedsmartcontractdeploywithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractDeployWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index bae79273d..71fdd7332 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated smart contract execution transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractexecution to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractExecution extends AbstractFeeDelegatedTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 79f2e63ec..1a3d8cc6e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.List; +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractExecutionWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index 2a1d205a0..854b8d7fd 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.crypto.KlaySignatureData; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated value transfer transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfer to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransfer extends AbstractFeeDelegatedTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index af0b57e00..5b6bee719 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated value transfer memo transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfermemo to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index 35085ba2a..df4ff670e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -37,6 +38,7 @@ * Represents a fee delegated value transfer memo with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransfermemowithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferMemoWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index b6d5c2f80..67e336cf1 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; @@ -38,6 +39,7 @@ * Represents a fee delegated value transfer with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransferwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index d1bbf76bb..66df650a5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.List; +@JsonIgnoreProperties(value = { "chainId" }) public class LegacyTransaction extends AbstractTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index d918979df..3197fb38c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -36,6 +37,7 @@ * Represents a smart contract deploy transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypesmartcontractdeploy to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class SmartContractDeploy extends AbstractTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index bffdcfee9..a2e14c83f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -35,6 +36,7 @@ * Represents a smart contract execution transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypesmartcontractexecution to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class SmartContractExecution extends AbstractTransaction { /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index 4b68fa400..f7d86ce07 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -35,6 +36,7 @@ * Represents a value transfer transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypevaluetransfer to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ValueTransfer extends AbstractTransaction { /** * The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index e9e3a9ad6..ce6ac8e4d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -16,6 +16,7 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.TransactionDecoder; @@ -35,6 +36,7 @@ * Represents a value transfer memo transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypevaluetransfermemo to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ValueTransferMemo extends AbstractTransaction { /** From 401adb6b52375d34aac0f563a84133c7be83e307 Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 5 Mar 2022 11:02:48 +0900 Subject: [PATCH 83/89] Add rpc tests (send and get transaction, receipt) for Ethereum transactions --- .../com/klaytn/caver/common/rpc/RpcTest.java | 183 +++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index 5ddae1ea3..1ac320a85 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -32,14 +32,15 @@ import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.response.PollingTransactionReceiptProcessor; import com.klaytn.caver.transaction.response.TransactionReceiptProcessor; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransfer; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransferWithRatio; -import com.klaytn.caver.transaction.type.ValueTransfer; +import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.utils.Convert; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.AbstractKeyring; import com.klaytn.caver.wallet.keyring.KeyringFactory; import com.klaytn.caver.wallet.keyring.SingleKeyring; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -263,6 +264,182 @@ public void valueTransferTest() throws IOException { } } + public static class signEthereumTransactionTest { + static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; + static SingleKeyring klayProviderKeyring = KeyringFactory.createFromPrivateKey(klayProvider); + + @BeforeClass + public static void unlockAccount() throws IOException { + RpcTest.importKey(klayProvider, "mypassword"); + RpcTest.unlockAccount(klayProviderKeyring.getAddress(), "mypassword"); + } + + String to = "0x7b65B75d204aBed71587c9E519a89277766EE1d0"; + String gas = "0xf4240"; + String gasPrice = "0x5d21dba00"; + String nonce; + String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void ethereumAccessListTest() throws IOException { + nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setTo(to) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumAccessList.sign(klayProviderKeyring); + + SignTransaction signTransaction = klay.signTransaction(ethereumAccessList).send(); + assertEquals(signTransaction.getResult().getRaw(), ethereumAccessList.getRLPEncoding()); + } + + @Test + public void ethereumDynamicFeeTest() throws IOException { + nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setTo(to) + .setGas(gas) + .setMaxPriorityFeePerGas(gasPrice) + .setMaxFeePerGas(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.sign(klayProviderKeyring); + + SignTransaction signTransaction = klay.signTransaction(ethereumDynamicFee).send(); + assertEquals(signTransaction.getResult().getRaw(), ethereumDynamicFee.getRLPEncoding()); + } + } + + public static class sendAndGetEthereumTransactionTest { + static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; + static SingleKeyring klayProviderKeyring = KeyringFactory.createFromPrivateKey(klayProvider); + + @BeforeClass + public static void unlockAccount() throws IOException { + RpcTest.importKey(klayProvider, "mypassword"); + RpcTest.unlockAccount(klayProviderKeyring.getAddress(), "mypassword"); + } + + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String gas = "0xf4240"; + String gasPrice = "0x5d21dba00"; + String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + private void checkTransactionFields(Transaction.TransactionData transactionData, boolean isDynamicFee) { + Assert.assertEquals(to, transactionData.getTo()); + Assert.assertEquals(gas, transactionData.getGas()); + if (isDynamicFee) { + Assert.assertEquals(gasPrice, transactionData.getMaxPriorityFeePerGas()); + Assert.assertEquals(gasPrice, transactionData.getMaxFeePerGas()); + } else { + Assert.assertEquals(gasPrice, transactionData.getGasPrice()); + } + Assert.assertEquals(accessList, transactionData.getAccessList()); + } + + private void checkTransactionReceiptFields(TransactionReceipt.TransactionReceiptData transactionReceiptData, boolean isDynamicFee) { + Assert.assertEquals(to, transactionReceiptData.getTo()); + Assert.assertEquals(gas, transactionReceiptData.getGas()); + if (isDynamicFee) { + Assert.assertEquals(gasPrice, transactionReceiptData.getMaxPriorityFeePerGas()); + Assert.assertEquals(gasPrice, transactionReceiptData.getMaxFeePerGas()); + } else { + Assert.assertEquals(gasPrice, transactionReceiptData.getGasPrice()); + } + Assert.assertEquals(accessList, transactionReceiptData.getAccessList()); + } + + @Test + public void ethereumAccessListTest() throws IOException, InterruptedException { + String nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setTo(to) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumAccessList.sign(klayProviderKeyring); + + Bytes32 result = klay.sendTransaction(ethereumAccessList).send(); + String transactionHash = result.getResult(); + Thread.sleep(2000); + Transaction transaction = klay.getTransactionByHash(transactionHash).send(); + checkTransactionFields(transaction.getResult(), false); + TransactionReceipt transactionReceipt = klay.getTransactionReceipt(transactionHash).send(); + checkTransactionReceiptFields(transactionReceipt.getResult(), false); + Assert.assertEquals(transactionHash, transactionReceipt.getResult().getTransactionHash()); + } + + + @Test + public void ethereumDynamicFeeTest() throws IOException, InterruptedException { + String nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setTo(to) + .setGas(gas) + .setMaxPriorityFeePerGas(gasPrice) + .setMaxFeePerGas(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.sign(klayProviderKeyring); + + Bytes32 result = klay.sendTransaction(ethereumDynamicFee).send(); + String transactionHash = result.getResult(); + Thread.sleep(2000); + Transaction transaction = klay.getTransactionByHash(transactionHash).send(); + checkTransactionFields(transaction.getResult(), true); + TransactionReceipt transactionReceipt = klay.getTransactionReceipt(transactionHash).send(); + checkTransactionReceiptFields(transactionReceipt.getResult(), true); + Assert.assertEquals(transactionHash, transactionReceipt.getResult().getTransactionHash()); + } + } + public static class signTransactionAsFeePayerTest { static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; static String feePayer = "1e558ea00698990d875cb69d3c8f9a234fe8eab5c6bd898488d851669289e178"; From c299be5a2d20274e8168c5f7854ef2c0d13064ce Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 5 Mar 2022 11:07:17 +0900 Subject: [PATCH 84/89] Fix comments --- .../com/klaytn/caver/transaction/type/EthereumDynamicFee.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java index d3d4ae49f..45168e38e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -289,7 +289,7 @@ public void setMaxFeePerGas(BigInteger maxFeePerGas) { @Override public String getRLPEncoding() { - // TransactionPayload = 0x7801 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + // TransactionPayload = 0x7802 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) this.validateOptionalValues(true); List rlpTypeList = new ArrayList<>(); @@ -462,7 +462,7 @@ public static EthereumDynamicFee decode(String rlpEncoded) { * @return EthereumDynamicFee */ public static EthereumDynamicFee decode(byte[] rlpEncoded) { - // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + // TxHashRLP = 0x7802 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) try { if ((rlpEncoded[0] << 8 | rlpEncoded[1]) != TransactionType.TxTypeEthereumDynamicFee.getType()) { throw new IllegalArgumentException("Invalid RLP-encoded tag - " + TransactionType.TxTypeEthereumDynamicFee.toString()); From a94cafc23500556baf6a79c7494fe457ad8ec961 Mon Sep 17 00:00:00 2001 From: Denver Date: Sat, 5 Mar 2022 20:00:36 +0900 Subject: [PATCH 85/89] Fix comment and add access modifier private to Builder member variables And remove un-necessary semi-colon --- .../transaction/type/EthereumAccessList.java | 4 +- .../transaction/type/EthereumDynamicFee.java | 48 +++++++++---------- .../wrapper/EthereumDynamicFeeWrapper.java | 4 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index b3e70f440..6e881cf0a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -456,7 +456,7 @@ public void appendSignatures(SignatureData signatureData) { if (v != 0 && v != 1) { throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); } - }; + } super.appendSignatures(signatureData); } @@ -481,7 +481,7 @@ public void appendSignatures(List signatureData) { if (v != 0 && v != 1) { throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); } - }; + } super.appendSignatures(signatureData); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java index 45168e38e..abc8f2c55 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -83,8 +83,8 @@ public static class Builder extends AbstractTransaction.Builder signatures, String to, String input, String value, AccessList accessList) { @@ -192,17 +192,17 @@ public EthereumDynamicFee(EthereumDynamicFee.Builder builder) { /** * Create a EthereumDynamicFee instance. * - * @param klaytnCall Klay RPC instance - * @param from The address of the sender. - * @param nonce A value used to uniquely identify a sender’s transaction. - * @param gas The maximum amount of gas the transaction is allowed to use. + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. * @param maxPriorityFeePerGas Max priority fee per gas. - * @param maxFeePerGas Max fee per gas. - * @param chainId Network ID - * @param signatures A Signature list - * @param to The account address that will receive the transferred value. - * @param input Data attached to the transaction, used for transaction execution. - * @param value The amount of KLAY in peb to be transferred. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. */ public EthereumDynamicFee(Klay klaytnCall, String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { super( @@ -524,7 +524,7 @@ public void appendSignatures(SignatureData signatureData) { if (v != 0 && v != 1) { throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); } - }; + } super.appendSignatures(signatureData); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java index 864608bef..7a07610da 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java @@ -26,7 +26,7 @@ /** * Represents a EthereumDynamicFeeWrapper * 1. This class wraps all of static methods of EthereumDynamicFee - * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + * 2. This class should be accessed via `caver.transaction.ethereumDynamicFee` */ public class EthereumDynamicFeeWrapper { /** @@ -79,7 +79,7 @@ public EthereumDynamicFee create(EthereumDynamicFee.Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param maxPriorityFeePerGas Max prirotiy fee per gas. + * @param maxPriorityFeePerGas Max priority fee per gas. * @param maxFeePerGas Max fee per gas. * @param chainId Network ID * @param signatures A Signature list From 277c8936dd80805875532da663059070225262ff Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 6 Mar 2022 23:09:07 +0900 Subject: [PATCH 86/89] Apply suggestions from code review Co-authored-by: Jamie <32922423+jimni1222@users.noreply.github.com> --- .../klaytn/caver/transaction/type/EthereumDynamicFee.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java index abc8f2c55..d9b343654 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -323,7 +323,7 @@ public String getCommonRLPEncodingForSignature() { * @return String */ @Override - public String getRLPEncodingForSignature() { // SigRLP = 0x01 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]) + public String getRLPEncodingForSignature() { // SigRLP = 0x02 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]) this.validateOptionalValues(true); @@ -410,7 +410,7 @@ public String combineSignedRawTransactions(List rlpEncoded) { } /** - * Fills empty optional transaction field.(gasPrice) + * Fills empty optional transaction fields.(maxPriorityFeePerGas and maxFeePerGas) * @throws IOException */ @Override @@ -560,7 +560,7 @@ public void appendSignatures(List signatureData) { */ @Override public String getTransactionHash() { - // TxHashRLP = 0x01 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + // TxHashRLP = 0x02 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) String rlpEncoded = this.getRLPEncoding(); byte[] rlpEncodedBytes = Numeric.hexStringToByteArray(rlpEncoded); byte[] detachedType = Arrays.copyOfRange(rlpEncodedBytes, 1, rlpEncodedBytes.length); From c14402a716fec93a441e4d2e3f61ace19281f453 Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 6 Mar 2022 23:09:40 +0900 Subject: [PATCH 87/89] Add comments for getRLPEncoding and getCommonRLPEncodingForSignature --- .../klaytn/caver/transaction/type/EthereumAccessList.java | 8 ++++++++ .../klaytn/caver/transaction/type/EthereumDynamicFee.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 6e881cf0a..880a739be 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -233,6 +233,10 @@ public void setGasPrice(BigInteger gasPrice) { } + /** + * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). + * @return String + */ @Override public String getRLPEncoding() { // TransactionPayload = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) @@ -257,6 +261,10 @@ public String getRLPEncoding() { return Numeric.toHexString(rawTx); } + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * @return String + */ @Override public String getCommonRLPEncodingForSignature() { return getRLPEncodingForSignature(); diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java index d9b343654..8932e78ee 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -287,6 +287,10 @@ public void setMaxFeePerGas(BigInteger maxFeePerGas) { } + /** + * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). + * @return String + */ @Override public String getRLPEncoding() { // TransactionPayload = 0x7802 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) @@ -312,6 +316,10 @@ public String getRLPEncoding() { return Numeric.toHexString(rawTx); } + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * @return String + */ @Override public String getCommonRLPEncodingForSignature() { return getRLPEncodingForSignature(); From c4d4ecafd344d185ab137ea9d790e480692a591b Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 6 Mar 2022 23:11:37 +0900 Subject: [PATCH 88/89] Fix wrong variables naming --- .../type/wrapper/EthereumAccessListWrapper.java | 12 ++++++------ .../type/wrapper/EthereumDynamicFeeWrapper.java | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java index 73758d26c..2341b3bfa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -48,9 +48,9 @@ public EthereumAccessListWrapper(Klay klaytnCall) { * @return EthereumAccessList */ public EthereumAccessList create(String rlpEncoded) { - EthereumAccessList legacyTransaction = EthereumAccessList.decode(rlpEncoded); - legacyTransaction.setKlaytnCall(this.klaytnCall); - return legacyTransaction; + EthereumAccessList ethereumAccessList = EthereumAccessList.decode(rlpEncoded); + ethereumAccessList.setKlaytnCall(this.klaytnCall); + return ethereumAccessList; } /** @@ -59,9 +59,9 @@ public EthereumAccessList create(String rlpEncoded) { * @return EthereumAccessList */ public EthereumAccessList create(byte[] rlpEncoded) { - EthereumAccessList legacyTransaction = EthereumAccessList.decode(rlpEncoded); - legacyTransaction.setKlaytnCall(this.klaytnCall); - return legacyTransaction; + EthereumAccessList ethereumAccessList = EthereumAccessList.decode(rlpEncoded); + ethereumAccessList.setKlaytnCall(this.klaytnCall); + return ethereumAccessList; } /** diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java index 7a07610da..083f281e3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java @@ -48,9 +48,9 @@ public EthereumDynamicFeeWrapper(Klay klaytnCall) { * @return EthereumDynamicFee */ public EthereumDynamicFee create(String rlpEncoded) { - EthereumDynamicFee legacyTransaction = EthereumDynamicFee.decode(rlpEncoded); - legacyTransaction.setKlaytnCall(this.klaytnCall); - return legacyTransaction; + EthereumDynamicFee ethereumDynamicFee = EthereumDynamicFee.decode(rlpEncoded); + ethereumDynamicFee.setKlaytnCall(this.klaytnCall); + return ethereumDynamicFee; } /** @@ -59,9 +59,9 @@ public EthereumDynamicFee create(String rlpEncoded) { * @return EthereumDynamicFee */ public EthereumDynamicFee create(byte[] rlpEncoded) { - EthereumDynamicFee legacyTransaction = EthereumDynamicFee.decode(rlpEncoded); - legacyTransaction.setKlaytnCall(this.klaytnCall); - return legacyTransaction; + EthereumDynamicFee ethereumDynamicFee = EthereumDynamicFee.decode(rlpEncoded); + ethereumDynamicFee.setKlaytnCall(this.klaytnCall); + return ethereumDynamicFee; } /** From c3ec4d50a8f676f833b45acdad3db29e34665cbe Mon Sep 17 00:00:00 2001 From: Denver Date: Sun, 6 Mar 2022 23:13:58 +0900 Subject: [PATCH 89/89] Collect setter and getter positions --- .../transaction/type/EthereumAccessList.java | 67 +++++---- .../transaction/type/EthereumDynamicFee.java | 131 +++++++++--------- 2 files changed, 98 insertions(+), 100 deletions(-) diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java index 880a739be..0269a7846 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -132,7 +132,6 @@ public static EthereumAccessList create(EthereumAccessList.Builder builder) { return new EthereumAccessList(builder); } - /** * Create a EthereumAccessList instance. * @@ -200,39 +199,6 @@ public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas setGasPrice(gasPrice); } - /** - * Getter function for gas price - * @return String - */ - public String getGasPrice() { - return gasPrice; - } - - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(String gasPrice) { - if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { - gasPrice = "0x"; - } - - if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { - throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); - } - - this.gasPrice = gasPrice; - } - - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - } - - /** * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). * @return String @@ -731,4 +697,37 @@ public void setAccessList(AccessList accessList) { } this.accessList = accessList; } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + } \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java index 8932e78ee..4cfba251d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -188,7 +188,6 @@ public EthereumDynamicFee(EthereumDynamicFee.Builder builder) { setMaxFeePerGas(builder.maxFeePerGas); } - /** * Create a EthereumDynamicFee instance. * @@ -222,71 +221,6 @@ public EthereumDynamicFee(Klay klaytnCall, String from, String nonce, String gas setMaxFeePerGas(maxFeePerGas); } - /** - * Getter function for maxPriorityFeePerGas - * @return String - */ - public String getMaxPriorityFeePerGas() { - return maxPriorityFeePerGas; - } - - /** - * Setter function for maxPriorityFeePerGas. - * @param maxPriorityFeePerGas Max priority fee per gas. - */ - public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { - if(maxPriorityFeePerGas == null || maxPriorityFeePerGas.isEmpty() || maxPriorityFeePerGas.equals("0x")) { - maxPriorityFeePerGas = "0x"; - } - - if(!maxPriorityFeePerGas.equals("0x") && !Utils.isNumber(maxPriorityFeePerGas)) { - throw new IllegalArgumentException("Invalid maxPriorityFeePerGas. : " + maxPriorityFeePerGas); - } - - this.maxPriorityFeePerGas = maxPriorityFeePerGas; - } - - /** - * Setter function for maxPriorityFeePerGas. - * @param maxPriorityFeePerGas Max priority fee per gas. - */ - public void setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { - setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); - } - - /** - * Getter function for maxFeePerGas. - * @return String - */ - public String getMaxFeePerGas() { - return maxFeePerGas; - } - - /** - * Setter function for maxFeePerGas. - * @param maxFeePerGas Max fee per gas. - */ - public void setMaxFeePerGas(String maxFeePerGas) { - if(maxFeePerGas == null || maxFeePerGas.isEmpty() || maxFeePerGas.equals("0x")) { - maxFeePerGas = "0x"; - } - - if(!maxFeePerGas.equals("0x") && !Utils.isNumber(maxFeePerGas)) { - throw new IllegalArgumentException("Invalid maxFeePerGas. : " + maxFeePerGas); - } - - this.maxFeePerGas = maxFeePerGas; - } - - /** - * Setter function for gas price. - * @param maxFeePerGas Max fee per gas. - */ - public void setMaxFeePerGas(BigInteger maxFeePerGas) { - setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); - } - - /** * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). * @return String @@ -799,4 +733,69 @@ public void setAccessList(AccessList accessList) { } this.accessList = accessList; } + + /** + * Getter function for maxPriorityFeePerGas + * @return String + */ + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + if(maxPriorityFeePerGas == null || maxPriorityFeePerGas.isEmpty() || maxPriorityFeePerGas.equals("0x")) { + maxPriorityFeePerGas = "0x"; + } + + if(!maxPriorityFeePerGas.equals("0x") && !Utils.isNumber(maxPriorityFeePerGas)) { + throw new IllegalArgumentException("Invalid maxPriorityFeePerGas. : " + maxPriorityFeePerGas); + } + + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { + setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); + } + + /** + * Getter function for maxFeePerGas. + * @return String + */ + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + /** + * Setter function for maxFeePerGas. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(String maxFeePerGas) { + if(maxFeePerGas == null || maxFeePerGas.isEmpty() || maxFeePerGas.equals("0x")) { + maxFeePerGas = "0x"; + } + + if(!maxFeePerGas.equals("0x") && !Utils.isNumber(maxFeePerGas)) { + throw new IllegalArgumentException("Invalid maxFeePerGas. : " + maxFeePerGas); + } + + this.maxFeePerGas = maxFeePerGas; + } + + /** + * Setter function for gas price. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(BigInteger maxFeePerGas) { + setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); + } + } \ No newline at end of file