From 03be99cebbe7403ed42ee417e99e591f441ce4d1 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Fri, 5 Aug 2022 14:53:06 -0500 Subject: [PATCH] working test infrastructure with 2.1 changes (so far) --- .github/workflows/ci.yml | 4 +- contrib/conf/stacks-l1-mocknet-double.toml | 15 +- core-contracts/Clarinet.toml | 1 + core-contracts/contracts/multi-miner.clar | 2 +- .../tests/hyperchains/multiminer_test.ts | 171 ++++++++++++------ 5 files changed, 131 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8585d305f..3693772bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,12 +173,12 @@ jobs: ref: hack/2.1 submodules: recursive - name: Build Clarinet with 2.1 - run: cargo build --release -p=clarinet-cli + run: cargo build -p=clarinet-cli - name: Upload Clarinet binary uses: actions/upload-artifact@v2 with: name: clarinet - path: ./target/release/clarinet + path: ./target/debug/clarinet clarinet-test: runs-on: ubuntu-latest needs: build-clarinet-2_1 diff --git a/contrib/conf/stacks-l1-mocknet-double.toml b/contrib/conf/stacks-l1-mocknet-double.toml index 69791c8f0..4c710ae16 100644 --- a/contrib/conf/stacks-l1-mocknet-double.toml +++ b/contrib/conf/stacks-l1-mocknet-double.toml @@ -12,21 +12,22 @@ use_test_genesis_chainstate = true chain = "bitcoin" mode = "mocknet" + [[burnchain.epochs]] -epoch_id = 0x02000 +epoch_name = "1.0" start_height = 0 -end_height = 1 [[burnchain.epochs]] -epoch_id = 0x02005 +epoch_name = "2.0" +start_height = 0 + +[[burnchain.epochs]] +epoch_name = "2.05" start_height = 1 -end_height = 2 [[burnchain.epochs]] -epoch_id = 0x0200a +epoch_name = "2.1" start_height = 2 -# i64::MAX -end_height = 9_223_372_036_854_775_807 [[ustx_balance]] # secret key = aaf57b4730f713cf942bc63f0801c4a62abe5a6ac8e3da10389f9ca3420b0dc701 diff --git a/core-contracts/Clarinet.toml b/core-contracts/Clarinet.toml index 33fb32e6d..05845e418 100644 --- a/core-contracts/Clarinet.toml +++ b/core-contracts/Clarinet.toml @@ -1,6 +1,7 @@ [project] name = "core-contracts" +# deno_import_file = "import_map.json" [contracts.trait-standards] path = "contracts/helper/trait-standards.clar" diff --git a/core-contracts/contracts/multi-miner.clar b/core-contracts/contracts/multi-miner.clar index dbc569a11..e5e32638b 100644 --- a/core-contracts/contracts/multi-miner.clar +++ b/core-contracts/contracts/multi-miner.clar @@ -58,7 +58,7 @@ ;; Remove this function in 2.1 ;; (define-private (to-consensus-buff (block-data { block: (buff 32), withdrawal-root: (buff 32), target-tip: (buff 32), multi-contract: principal })) (ok 0x00)) -(define-private (make-block-commit-hash (block-data { block: (buff 32), withdrawal-root: (buff 32), target-tip: (buff 32) })) +(define-read-only (make-block-commit-hash (block-data { block: (buff 32), withdrawal-root: (buff 32), target-tip: (buff 32) })) (let ((data-buff (unwrap-panic (to-consensus-buff (merge block-data { multi-contract: CONTRACT_ADDRESS })))) (data-hash (sha256 data-buff)) ;; in 2.0, this is a constant: 0xe2f4d0b1eca5f1b4eb853cd7f1c843540cfb21de8bfdaa59c504a6775cd2cfe9 diff --git a/core-contracts/tests/hyperchains/multiminer_test.ts b/core-contracts/tests/hyperchains/multiminer_test.ts index 1f8d05be0..8223ba5de 100644 --- a/core-contracts/tests/hyperchains/multiminer_test.ts +++ b/core-contracts/tests/hyperchains/multiminer_test.ts @@ -1,6 +1,8 @@ import { Clarinet, Tx, Chain, Account, Contract, types } from 'https://deno.land/x/clarinet@v0.31.0/index.ts'; import { assertEquals } from "https://deno.land/std@0.90.0/testing/asserts.ts"; import { createHash } from "https://deno.land/std@0.107.0/hash/mod.ts"; +import { decode as decHex, encode as encHex } from "https://deno.land/std@0.149.0/encoding/hex.ts"; +// import * as secp from "https://deno.land/x/secp256k1@1.6.3/mod.ts"; const ERR_SIGNER_APPEARS_TWICE = 101; const ERR_NOT_ENOUGH_SIGNERS = 102; @@ -8,6 +10,48 @@ const ERR_INVALID_SIGNATURE = 103; const ERR_UNAUTHORIZED_CONTRACT_CALLER = 104; const ERR_MINER_ALREADY_SET = 105; +function fromHex(input: string) { + const hexBytes = new TextEncoder().encode(input); + return decHex(hexBytes); +} + +function toHex(input: Uint8Array) { + const hexBytes = encHex(input); + return new TextDecoder().decode(hexBytes); +} + +function buffFromHex(input: string) { + return types.buff(fromHex(input)); +} + +// function sign(messageHash: string, signer: string) { +// const result = secp.signSync(messageHash, signer.slice(0, -2), { der: false, recovered: true }); +// return `0x${toHex(result[0])}0${result[1]}` +// } + + +function sign(messageHash: string, signer: string) { + const signature_map = new Map(); + signature_map.set('(7acbdb54d0798e8b2930a0b2adeea24e46813edadd04f3497f020f328c77dbb2, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0xea418d4ae220a5c4afdc0c8e21c28e26a338117fee39f878ebb1251c56f74ac3da64b20e66fca8b697a9751ba0599ba2a5210add0f7a43e5c2a230d3f4df051600'); + signature_map.set('(63ba42b8727b1f01d78de3b7bfad61d6f3b146695350457e1dbb252861135461, 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001)', '0xb459af09a1d6200766e32f4513392da7ffd374b6ba925a41c26854c5e02c7623ec419b4f68eae501f36fccfd9a40c83489f232446ae6dc04c075efc17b5b6eaa00'); + signature_map.set('(63ba42b8727b1f01d78de3b7bfad61d6f3b146695350457e1dbb252861135461, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0x9874cae8caf04ac52bf0af74c79edd58f81164d78245e2e197d2832b60e0921b3dff0430720b725956df91672eab183b85402c164a6819eb7ac0a0850a1d21f801'); + signature_map.set('(822caf4a720c0d1db92bb948521650dbeabe5fa94b5817f718d6e4a715d2b583, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0x549f505da94df6f56df8da10eb2b5585a95ceca2ca3a6168176840215a330c5825ab16f6eb8b48474006f9eb2ce3fd61a5552ccb53755aaccdee1c0fbc6fcb8e01'); + signature_map.set('(822caf4a720c0d1db92bb948521650dbeabe5fa94b5817f718d6e4a715d2b583, 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001)', '0xc223b412c0e2ae1dc28c981b23de7d8a601c94cc190e7e37227ecd47fe4be8a628394f826cb8a25c133a599ef2fc3796155c5ecc924cbc35fff4ab6a0cc685ff01'); + signature_map.set('(cad85aa65df028d53b32303c7c1204b660102319720295cd3cc3fd71553fe217, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0xc8ee11648c48ae85f1b8003c53690efc9aab20ce05526643fb0ccc3f2ac46cee174c51ba62f5e06cc5223cee0efccb7c1eec20e1fe22bb6654daab78e067f16a00'); + signature_map.set('(cad85aa65df028d53b32303c7c1204b660102319720295cd3cc3fd71553fe217, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0xc8ee11648c48ae85f1b8003c53690efc9aab20ce05526643fb0ccc3f2ac46cee174c51ba62f5e06cc5223cee0efccb7c1eec20e1fe22bb6654daab78e067f16a00'); + signature_map.set('(cad85aa65df028d53b32303c7c1204b660102319720295cd3cc3fd71553fe217, 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001)', '0x7090b64cc6c63fe675de25d0c890bbac58e5ca93e9bd2c9c40a05229ef464fae6232490c2d8c66565a22a97d2155aa2d12b6434e8ba75c081e11b9f9d9a4ef7500'); + signature_map.set('(84b70510646b9b5967d637fca9fd9cf29d957848d52577348d09ca59432a867a, 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101)', '0x11796554ddafc42d3aae1738ee1ddf573988404d5c990c574cbdefb73d31b8995e54abcda499738aaeffb6432fe37f1c2bc749471a3815fea131eb4ba76da14c00'); + signature_map.set('(84b70510646b9b5967d637fca9fd9cf29d957848d52577348d09ca59432a867a, 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001)', '0xf34be14d6411debab873c0a79033d4a4a6e39a8acb46eb214d2ac112148698011a62abf466cc3fbb2b5f7e144c631abd86547c3a0763675315bad32531e8084e01'); + + const key = `(${messageHash}, ${signer})`; + if (signature_map.has(key)) { + return signature_map.get(key)!; + } else { + console.log(`sign("${messageHash}", "${signer}")`); + throw "No known signature for request"; + } +} + Clarinet.test({ name: "Test multi-party commit when one party submits transactions and other party is signatory", async fn(chain: Chain, accounts: Map, contracts: Map) { @@ -48,65 +92,72 @@ Clarinet.test({ alice.address), ]); - // to generate: stacks-inspect secp256k1-sign e2f4d0b1eca5f1b4eb853cd7f1c843540cfb21de8bfdaa59c504a6775cd2cfe9 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101 - let signatorySigned = "0xac04279c1a6fa31e87d6ee54790c4016a7c8b90587d9c71938b4f4eae2d448280435ebb9b9b7462e54d6eff23350aa8272e8d9f8f02332883dd845c14c19d5c300"; - // to generate: stacks-inspect secp256k1-sign e2f4d0b1eca5f1b4eb853cd7f1c843540cfb21de8bfdaa59c504a6775cd2cfe9 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001 - let nonSignatorySigned = "0xee504bc280ff1564195638ed2d86e74994c75833432dff9be56a0880c0e52b146582a0b46fbda9640f32e2fc44a6cd1521ea2ef4816d2b1a48a36bb70f1b37fd00"; - let badSignature = "0x0ca28913fe5d08da93f4738cee281747b00a76d6e1d266bcfa17d87b9542e0c979a3065dff06518bbd7d5d08059be79a41e3d5a74f39738bf1183f56091e5d7c01"; + const block_hash_1 = "0x0000000000000001000000000000000100000000000000010000000000000001"; + const withdrawal_root_1 = "0x0000000000000001000000000000000100000000000000010000000000000002"; const id_header_hash_1 = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const commit_data_1 = types.tuple({ + "block": buffFromHex(block_hash_1.slice(2)), + "withdrawal-root": buffFromHex(withdrawal_root_1.slice(2)), + "target-tip": id_header_hash_1 + }); + const message_hash_1 = chain.callReadOnlyFn('multi-miner', 'make-block-commit-hash', [commit_data_1], alice.address).result.toString(); + const signatory_signed_1 = sign(message_hash_1.slice(2), signatory.secretKey); + let block = chain.mineBlock([ // Successfully commit block with alice and signatory as the miners Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash_1 }), - types.list([signatorySigned]), + commit_data_1, + types.list([signatory_signed_1]), ], alice.address), ]); block.receipts[0].result .expectOk() - .expectBuff(new Uint8Array([0, 1, 1, 1, 1])); + .expectBuff(fromHex(block_hash_1.slice(2))); const id_header_hash_2 = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const commit_data_2 = types.tuple({ + "block": buffFromHex(block_hash_1.slice(2)), + "withdrawal-root": buffFromHex(withdrawal_root_1.slice(2)), + "target-tip": id_header_hash_2 + }); + const message_hash_2 = chain.callReadOnlyFn('multi-miner', 'make-block-commit-hash', [commit_data_2], alice.address).result.toString(); + const non_signatory_signed_2 = sign(message_hash_2.slice(2), nonSignatory.secretKey); + const signatory_signed_2 = sign(message_hash_2.slice(2), signatory.secretKey); + // provide a totally invalid signature + const bad_signature_1 = "0x500f511da88df8856d77da10eb2ff585a95cecddca3a6ee817aa40215a330c5825ab16f6eb8b48474006f9eb2ce3fd61a5552ccb53755aaccdee1c0fbc6fcb8e88"; + // sign the wrong hash + const bad_signature_2 = sign(message_hash_1.slice(2), signatory.secretKey); block = chain.mineBlock([ // Fail to commit block with alice and nonSignatory as the miners Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash_2 }), - types.list([nonSignatorySigned]), + commit_data_2, + types.list([non_signatory_signed_2]), ], alice.address), // Fail to commit block with a bad signature Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash_2 }), - types.list([badSignature]), + commit_data_2, + types.list([bad_signature_1]), ], alice.address), // Fail to commit block with non-unique signers Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash_2 }), - types.list([signatorySigned, signatorySigned]), + commit_data_2, + types.list([signatory_signed_2, signatory_signed_2]), ], alice.address), // Fail to commit block with not-enough signers when sender isn't a miner Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash_2 }), - types.list([signatorySigned]), + commit_data_2, + types.list([signatory_signed_2]), ], bob.address), ]); @@ -166,64 +217,80 @@ Clarinet.test({ alice.address), ]); - // to generate: stacks-inspect secp256k1-sign e2f4d0b1eca5f1b4eb853cd7f1c843540cfb21de8bfdaa59c504a6775cd2cfe9 7deca54bdb555e4d9aa2310cb9ed8829d59e2098cbc06f62238cdd8fcb08c08101 - let signatory1Signed = "0xac04279c1a6fa31e87d6ee54790c4016a7c8b90587d9c71938b4f4eae2d448280435ebb9b9b7462e54d6eff23350aa8272e8d9f8f02332883dd845c14c19d5c300"; - // to generate: stacks-inspect secp256k1-sign e2f4d0b1eca5f1b4eb853cd7f1c843540cfb21de8bfdaa59c504a6775cd2cfe9 003f8c631e98bf52b8dfa36f02df0aaab85dfefc5d8bedb41bc5184afdc4a16001 - let signatory2Signed = "0xee504bc280ff1564195638ed2d86e74994c75833432dff9be56a0880c0e52b146582a0b46fbda9640f32e2fc44a6cd1521ea2ef4816d2b1a48a36bb70f1b37fd00"; - let badSignature = "0x0ca28913fe5d08da93f4738cee281747b00a76d6e1d266bcfa17d87b9542e0c979a3065dff06518bbd7d5d08059be79a41e3d5a74f39738bf1183f56091e5d7c01"; + const block_hash_1 = "0x0000000200000001000000000000000100000000000000010000000000000001"; + const withdrawal_root_1 = "0x0000000000000001000000000000000100000000400000010000000000000002"; - let id_header_hash = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + + const id_header_hash_1 = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const commit_data_1 = types.tuple({ + "block": buffFromHex(block_hash_1.slice(2)), + "withdrawal-root": buffFromHex(withdrawal_root_1.slice(2)), + "target-tip": id_header_hash_1 + }); + const message_hash_1 = chain.callReadOnlyFn('multi-miner', 'make-block-commit-hash', [commit_data_1], alice.address).result.toString(); + const signatory1_sig_1 = sign(message_hash_1.slice(2), signatory1.secretKey); + const signatory2_sig_1 = sign(message_hash_1.slice(2), signatory2.secretKey); let block = chain.mineBlock([ // Successfully commit block with alice as a sender and signatory1/2 as the miners Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash }), - types.list([signatory1Signed, signatory2Signed]), + commit_data_1, + types.list([signatory2_sig_1, signatory1_sig_1]), ], alice.address), ]); block.receipts[0].result .expectOk() - .expectBuff(new Uint8Array([0, 1, 1, 1, 1])); + .expectBuff(fromHex(block_hash_1.slice(2))); + + const id_header_hash_2 = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const commit_data_2 = types.tuple({ + "block": buffFromHex(block_hash_1.slice(2)), + "withdrawal-root": buffFromHex(withdrawal_root_1.slice(2)), + "target-tip": id_header_hash_2 + }); + const message_hash_2 = chain.callReadOnlyFn('multi-miner', 'make-block-commit-hash', [commit_data_2], alice.address).result.toString() + const signatory1_sig_2 = sign(message_hash_2.slice(2), signatory1.secretKey); + const signatory2_sig_2 = sign(message_hash_2.slice(2), signatory2.secretKey); - id_header_hash = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); block = chain.mineBlock([ // Successfully commit block with bob as a sender and signatory1/2 as the miners Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash }), - types.list([signatory1Signed, signatory2Signed]), + commit_data_2, + types.list([signatory1_sig_2, signatory2_sig_2]), ], bob.address), ]); block.receipts[0].result .expectOk() - .expectBuff(new Uint8Array([0, 1, 1, 1, 1])); + .expectBuff(fromHex(block_hash_1.slice(2))); // now test failure modes - id_header_hash = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const id_header_hash_3 = chain.callReadOnlyFn('test-helpers', 'get-id-header-hash', [], alice.address).result.expectOk().toString(); + const commit_data_3 = types.tuple({ + "block": buffFromHex(block_hash_1.slice(2)), + "withdrawal-root": buffFromHex(withdrawal_root_1.slice(2)), + "target-tip": id_header_hash_3 + }); + const message_hash_3 = chain.callReadOnlyFn('multi-miner', 'make-block-commit-hash', [commit_data_3], alice.address).result.toString(); + const signatory1_sig_3 = sign(message_hash_3.slice(2), signatory1.secretKey); + const signatory2_sig_3 = sign(message_hash_3.slice(2), signatory2.secretKey); + block = chain.mineBlock([ // Fail to commit block with alice and signatory as the miners Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash }), - types.list([signatory1Signed]), + commit_data_3, + types.list([signatory2_sig_3]), ], alice.address), // Fail to commit block with non-unique signers Tx.contractCall("multi-miner", "commit-block", [ - types.tuple({ "block": types.buff(new Uint8Array([0, 1, 1, 1, 1])), - "withdrawal-root": types.buff(new Uint8Array([0, 1, 1, 1, 2])), - "target-tip": id_header_hash }), - types.list([signatory1Signed, signatory2Signed]), + commit_data_3, + types.list([signatory1_sig_3, signatory2_sig_3]), ], signatory1.address), ]);