Skip to content

Commit

Permalink
feature: handling and externalisation improvements for account nonce …
Browse files Browse the repository at this point in the history
…updates (#2176)

* chore: all necessary stuff for release

Signed-off-by: svetoslav-nikol0v <[email protected]>

* feature: add signerNonce field

Signed-off-by: svetoslav-nikol0v <[email protected]>

---------

Signed-off-by: svetoslav-nikol0v <[email protected]>
  • Loading branch information
svetoslav-nikol0v authored Feb 29, 2024
1 parent ff2a699 commit a58be38
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ updates:
schedule:
interval: monthly
open-pull-requests-limit: 10
versioning-strategy: increase
ignore:
# For all packages, ignore all minor & patch updates
- dependency-name: "*"
Expand All @@ -20,6 +21,7 @@ updates:
schedule:
interval: monthly
open-pull-requests-limit: 10
versioning-strategy: increase
ignore:
# For all packages, ignore all minor & patch updates
- dependency-name: "*"
Expand All @@ -30,6 +32,7 @@ updates:
schedule:
interval: monthly
open-pull-requests-limit: 10
versioning-strategy: increase
ignore:
# For all packages, ignore all minor & patch updates
- dependency-name: "*"
Expand All @@ -40,6 +43,7 @@ updates:
schedule:
interval: monthly
open-pull-requests-limit: 5
versioning-strategy: increase
ignore:
# For all packages, ignore all minor & patch updates
- dependency-name: "*"
Expand All @@ -50,6 +54,7 @@ updates:
schedule:
interval: monthly
open-pull-requests-limit: 5
versioning-strategy: increase
ignore:
# For all packages, ignore all minor & patch updates
- dependency-name: "*"
Expand Down
27 changes: 24 additions & 3 deletions .github/workflows/publish_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defaults:
shell: bash

permissions:
contents: read
contents: write

jobs:
validate-release:
Expand All @@ -30,6 +30,7 @@ jobs:
tag: ${{ steps.tag.outputs.name }}
version: ${{ steps.tag.outputs.version }}
prerelease: ${{ steps.tag.outputs.prerelease }}
type: ${{ steps.tag.outputs.type }}
steps:
- name: Checkout Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Expand Down Expand Up @@ -71,10 +72,22 @@ jobs:
RELEASE_VERSION="$(semver get release "${REF_NAME}")"
PREREL_VERSION="$(semver get prerel "${REF_NAME}")"
PREREL_VERSION_LC="$(printf "%s" "${PREREL_VERSION}" | tr '[:upper:]' '[:lower:]')"
IS_PRERELEASE="false"
[[ -n "${PREREL_VERSION}" ]] && IS_PRERELEASE="true"
PREREL_TYPE="unknown"
if [[ "${IS_PRERELEASE}" == "true" ]]; then
if [[ "${PREREL_VERSION_LC}" =~ "beta" ]]; then
PREREL_TYPE="beta"
else
PREREL_TYPE="unknown"
fi
else
PREREL_TYPE="production"
fi
FINAL_VERSION="${RELEASE_VERSION}"
[[ -n "${PREREL_VERSION}" ]] && FINAL_VERSION="${RELEASE_VERSION}-${PREREL_VERSION}"
Expand All @@ -83,6 +96,7 @@ jobs:
echo "name=${TAG_NAME}" >>"${GITHUB_OUTPUT}"
echo "version=${FINAL_VERSION}" >>"${GITHUB_OUTPUT}"
echo "prerelease=${IS_PRERELEASE}" >>"${GITHUB_OUTPUT}"
echo "type=${PREREL_TYPE}" >>"${GITHUB_OUTPUT}"
- name: Validate Tag and Package Versions
run: |
Expand All @@ -92,6 +106,11 @@ jobs:
exit 1
fi
if [[ "${{ steps.tag.outputs.type }}" != "production" && "${{ steps.tag.outputs.type }}" != "beta" ]]; then
echo "::error title=Unsupported PreRelease::The tag '${{ steps.tag.outputs.name }}' is an unsupported prerelease tag. Only 'beta' prereleases are supported."
exit 2
fi
run-safety-checks:
name: Safety Checks
runs-on: [self-hosted, Linux, medium, ephemeral]
Expand Down Expand Up @@ -158,6 +177,8 @@ jobs:
run: |
PUBLISH_ARGS="--access public --no-git-checks"
[[ "${{ github.event.inputs.dry-run-enabled }}" == "true" ]] && PUBLISH_ARGS="${PUBLISH_ARGS} --dry-run"
[[ "${{ needs.validate-release.outputs.prerelease }}" == "true" ]] && PUBLISH_ARGS="${PUBLISH_ARGS} --tag ${{ needs.validate-release.outputs.type }}"
echo "args=${PUBLISH_ARGS}" >>"${GITHUB_OUTPUT}"
# Add the registry authentication stanza with variable substitution to the .npmrc configuration file.
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >>".npmrc"
Expand All @@ -172,7 +193,7 @@ jobs:
if: ${{ github.event.inputs.dry-run-enabled != 'true' }}
with:
tag: ${{ steps.validate-release.outputs.tag }}
prerelease: ${{ steps.validate-release.outputs.prerelease == 'true' }}
prerelease: ${{ needs.validate-release.outputs.prerelease == 'true' }}
draft: false
generateReleaseNotes: true
skipIfReleaseExists: true
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@ethersproject/rlp": "^5.7.0",
"@grpc/grpc-js": "1.8.2",
"@hashgraph/cryptography": "1.4.8-beta.5",
"@hashgraph/proto": "2.14.0-beta.3",
"@hashgraph/proto": "2.14.0-beta.4",
"axios": "^1.6.4",
"bignumber.js": "^9.1.1",
"bn.js": "^5.1.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/proto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hashgraph/proto",
"version": "2.14.0-beta.3",
"version": "2.14.0-beta.4",
"description": "Protobufs for the Hedera™ Hashgraph SDK",
"main": "lib/index.js",
"browser": "src/index.js",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/contract/ContractFunctionResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default class ContractFunctionResult {
* @param {?AccountId} result.senderAccountId
* @param {ContractStateChange[]} result.stateChanges
* @param {ContractNonceInfo[]} result.contractNonces
* @param {Long | null} result.signerNonce
*/
constructor(result) {
/**
Expand Down Expand Up @@ -149,6 +150,12 @@ export default class ContractFunctionResult {
* This is always empty in a ContractCallLocalResponse#ContractFunctionResult message, since no internal creations can happen in a static EVM call.
*/
this.contractNonces = result.contractNonces;

/**
* If not null this field specifies what the value of the signer account nonce is post transaction execution.
* For transactions that don't update the signer nonce (like HAPI ContractCall and ContractCreate transactions) this field should be null.
*/
this.signerNonce = result.signerNonce;
}

/**
Expand Down Expand Up @@ -204,6 +211,12 @@ export default class ContractFunctionResult {
).map((contractNonce) =>
ContractNonceInfo._fromProtobuf(contractNonce),
),
signerNonce:
result.signerNonce != null
? result.signerNonce.value
? result.signerNonce.value
: null
: null,
});
}

Expand Down Expand Up @@ -1053,6 +1066,12 @@ export default class ContractFunctionResult {
contractNonces: this.contractNonces.map((contractNonce) =>
contractNonce._toProtobuf(),
),
signerNonce:
this.signerNonce != null
? {
value: this.signerNonce,
}
: null,
};
}
}
166 changes: 166 additions & 0 deletions test/integration/EthereumTransactionIntegrationTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {
FileCreateTransaction,
ContractFunctionParameters,
ContractCreateTransaction,
EthereumTransaction,
PrivateKey,
TransferTransaction,
Hbar,
TransactionResponse,
TransactionReceipt,
FileId,
ContractId,
Status,
TransactionRecord,
} from "../../src/exports.js";
import { SMART_CONTRACT_BYTECODE } from "./contents.js";
import * as rlp from "@ethersproject/rlp";
import IntegrationTestEnv from "./client/NodeIntegrationTestEnv.js";
import * as hex from "../../src/encoding/hex.js";

describe.only("EthereumTransactionIntegrationTest", function () {

Check warning on line 21 in test/integration/EthereumTransactionIntegrationTest.js

View workflow job for this annotation

GitHub Actions / Integration Tests on Node 16

Unexpected exclusive mocha test

Check warning on line 21 in test/integration/EthereumTransactionIntegrationTest.js

View workflow job for this annotation

GitHub Actions / Integration Tests on Node 18

Unexpected exclusive mocha test

Check warning on line 21 in test/integration/EthereumTransactionIntegrationTest.js

View workflow job for this annotation

GitHub Actions / Build using Node 16

Unexpected exclusive mocha test

Check warning on line 21 in test/integration/EthereumTransactionIntegrationTest.js

View workflow job for this annotation

GitHub Actions / Build using Node 18

Unexpected exclusive mocha test

Check warning on line 21 in test/integration/EthereumTransactionIntegrationTest.js

View workflow job for this annotation

GitHub Actions / Test using Node 16

Unexpected exclusive mocha test
let env, operatorKey, wallet, contractAddress, operatorId;

before(async function () {
env = await IntegrationTestEnv.new();
wallet = env.wallet;
operatorKey = wallet.getAccountKey();
operatorId = wallet.getAccountId();
});

it("Signer nonce changed on Ethereum transaction", async function () {
this.timeout(120000);

try {
const fileResponse = await (
await (
await new FileCreateTransaction()
.setKeys([wallet.getAccountKey()])
.setContents(SMART_CONTRACT_BYTECODE)
.setMaxTransactionFee(new Hbar(2))
.freezeWithSigner(wallet)
).signWithSigner(wallet)
).executeWithSigner(wallet);
expect(fileResponse).to.be.instanceof(TransactionResponse);

const fileReceipt = await fileResponse.getReceiptWithSigner(wallet);
expect(fileReceipt).to.be.instanceof(TransactionReceipt);
expect(fileReceipt.status).to.be.equal(Status.Success);
const fileId = fileReceipt.fileId;
expect(fileId).to.be.instanceof(FileId);

const contractResponse = await (
await (
await new ContractCreateTransaction()
.setAdminKey(operatorKey)
.setGas(200000)
.setConstructorParameters(
new ContractFunctionParameters()
.addString("Hello from Hedera.")
._build(),
)
.setBytecodeFileId(fileId)
.setContractMemo("[e2e::ContractCreateTransaction]")
.freezeWithSigner(wallet)
).signWithSigner(wallet)
).executeWithSigner(wallet);
expect(contractResponse).to.be.instanceof(TransactionResponse);
const contractReceipt =
await contractResponse.getReceiptWithSigner(wallet);
expect(contractReceipt).to.be.instanceof(TransactionReceipt);
expect(contractReceipt.status).to.be.equal(Status.Success);
const contractId = contractReceipt.contractId;
expect(contractId).to.be.instanceof(ContractId);
contractAddress = contractId.toSolidityAddress();
} catch (error) {
console.error(error);
}

const type = "02";
const chainId = hex.decode("012a");
const nonce = new Uint8Array();
const maxPriorityGas = hex.decode("00");
const maxGas = hex.decode("d1385c7bf0");
const gasLimit = hex.decode("0249f0");
const value = new Uint8Array();
const to = hex.decode(contractAddress);
const callData = new ContractFunctionParameters()
.addString("new message")
._build("setMessage");
const accessList = [];

const encoded = rlp
.encode([
chainId,
nonce,
maxPriorityGas,
maxGas,
gasLimit,
to,
value,
callData,
accessList,
])
.substring(2);
expect(typeof encoded).to.equal("string");

const privateKey = PrivateKey.generateECDSA();
expect(privateKey).to.be.instanceof(PrivateKey);

const accountAlias = privateKey.publicKey.toEvmAddress();

const transfer = await new TransferTransaction()
.addHbarTransfer(operatorId, new Hbar(10).negated())
.addHbarTransfer(accountAlias, new Hbar(10))
.setMaxTransactionFee(new Hbar(1))
.freezeWithSigner(wallet);

const transferResponse = await transfer.executeWithSigner(wallet);
expect(transferResponse).to.be.instanceof(TransactionResponse);
const transferReceipt =
await transferResponse.getReceiptWithSigner(wallet);
expect(transferReceipt).to.be.instanceof(TransactionReceipt);
expect(transferReceipt.status).to.be.equal(Status.Success);

const signedBytes = privateKey.sign(hex.decode(type + encoded));
const middleOfSignedBytes = signedBytes.length / 2;
const r = signedBytes.slice(0, middleOfSignedBytes);
const s = signedBytes.slice(middleOfSignedBytes, signedBytes.length);
const v = hex.decode("01"); // recovery id

const data = rlp
.encode([
chainId,
nonce,
maxPriorityGas,
maxGas,
gasLimit,
to,
value,
callData,
accessList,
v,
r,
s,
])
.substring(2);
expect(typeof data).to.equal("string");
const ethereumData = hex.decode(type + data);
expect(ethereumData.length).to.be.gt(0);

const response = await (
await (
await new EthereumTransaction()
.setEthereumData(ethereumData)
.freezeWithSigner(wallet)
).signWithSigner(wallet)
).executeWithSigner(wallet);
const record = await response.getRecordWithSigner(wallet);
expect(record).to.be.instanceof(TransactionRecord);
expect(response).to.be.instanceof(TransactionResponse);

const receipt = await response.getReceiptWithSigner(wallet);
expect(receipt).to.be.instanceof(TransactionReceipt);
expect(receipt.status).to.be.equal(Status.Success);
});
});
Loading

0 comments on commit a58be38

Please sign in to comment.