From 54ec435c03b8a108db51ce73cd49369f27815abf Mon Sep 17 00:00:00 2001 From: janniks Date: Wed, 13 Dec 2023 17:53:06 +0100 Subject: [PATCH] fix: implement latest tenure change spec --- packages/transactions/src/payload.ts | 48 +++++++++++++++++---- packages/transactions/tests/builder.test.ts | 4 +- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/transactions/src/payload.ts b/packages/transactions/src/payload.ts index 85f8f5f06..69d88e5a9 100644 --- a/packages/transactions/src/payload.ts +++ b/packages/transactions/src/payload.ts @@ -262,20 +262,36 @@ export function createNakamotoCoinbasePayload( export enum TenureChangeCause { /** A valid winning block-commit */ BlockFound = 0, - /** No winning block-commits */ - NoBlockFound = 1, - /** A "null miner" won the block-commit */ - NullMiner = 2, + /** The next burnchain block is taking too long, so extend the runtime budget */ + Extended = 1, } export interface TenureChangePayload { readonly type: StacksMessageType.Payload; readonly payloadType: PayloadType.TenureChange; + /** + * The consensus hash of this tenure (hex string). Corresponds to the + * sortition in which the miner of this block was chosen. It may be the case + * that this miner's tenure gets _extended_ acrosssubsequent sortitions; if + * this happens, then this `consensus_hash` value _remains the same _as the + * sortition in which the winning block-commit was mined. + */ + readonly tenureHash: string; + /** + * The consensus hash (hex string) of the previous tenure. Corresponds to the + * sortition of the previous winning block-commit. + */ + readonly previousTenureHash: string; + /** + * Current consensus hash (hex string) on the underlying burnchain. + * Corresponds to the last-seen sortition. + */ + readonly burnViewHash: string; /** Stacks block hash (hex string) */ readonly previousTenureEnd: string; - /** Number of blocks produced in the previous tenure */ + /** The number of blocks produced since the last sortition-linked tenure */ readonly previousTenureBlocks: number; - /** Cause of change in mining tenure */ + /** The cause of change in mining tenure */ readonly cause: TenureChangeCause; /** The public key hash of the current tenure (hex string) */ readonly publicKeyHash: string; @@ -286,6 +302,9 @@ export interface TenureChangePayload { } export function createTenureChangePayload( + tenureHash: string, + previousTenureHash: string, + burnViewHash: string, previousTenureEnd: string, previousTenureBlocks: number, cause: TenureChangeCause, @@ -296,6 +315,9 @@ export function createTenureChangePayload( return { type: StacksMessageType.Payload, payloadType: PayloadType.TenureChange, + tenureHash, + previousTenureHash, + burnViewHash, previousTenureEnd, previousTenureBlocks, cause, @@ -351,15 +373,17 @@ export function serializePayload(payload: PayloadInput): Uint8Array { bytesArray.push(payload.vrfProof); break; case PayloadType.TenureChange: + bytesArray.push(hexToBytes(payload.tenureHash)); + bytesArray.push(hexToBytes(payload.previousTenureHash)); + bytesArray.push(hexToBytes(payload.burnViewHash)); bytesArray.push(hexToBytes(payload.previousTenureEnd)); bytesArray.push(writeUInt32BE(new Uint8Array(4), payload.previousTenureBlocks)); bytesArray.push(writeUInt8(new Uint8Array(1), payload.cause)); bytesArray.push(hexToBytes(payload.publicKeyHash)); + bytesArray.push(hexToBytes(payload.signature)); const signers = hexToBytes(payload.signers); bytesArray.push(writeUInt32BE(new Uint8Array(4), signers.byteLength)); // signers length bytesArray.push(signers); - - bytesArray.push(hexToBytes(payload.signature)); break; } @@ -425,16 +449,22 @@ export function deserializePayload(bytesReader: BytesReader): Payload { return createNakamotoCoinbasePayload(coinbaseBytes, recipient, vrfProof); } case PayloadType.TenureChange: + const tenureHash = bytesToHex(bytesReader.readBytes(20)); + const previousTenureHash = bytesToHex(bytesReader.readBytes(20)); + const burnViewHash = bytesToHex(bytesReader.readBytes(20)); const previousTenureEnd = bytesToHex(bytesReader.readBytes(32)); const previousTenureBlocks = bytesReader.readUInt32BE(); const cause = bytesReader.readUInt8Enum(TenureChangeCause, n => { throw new Error(`Cannot recognize TenureChangeCause: ${n}`); }); const publicKeyHash = bytesToHex(bytesReader.readBytes(20)); + const signature = bytesToHex(bytesReader.readBytes(65)); const signersLength = bytesReader.readUInt32BE(); const signers = bytesToHex(bytesReader.readBytes(signersLength)); - const signature = bytesToHex(bytesReader.readBytes(65)); return createTenureChangePayload( + tenureHash, + previousTenureHash, + burnViewHash, previousTenureEnd, previousTenureBlocks, cause, diff --git a/packages/transactions/tests/builder.test.ts b/packages/transactions/tests/builder.test.ts index 47f4d2631..1366e0e6d 100644 --- a/packages/transactions/tests/builder.test.ts +++ b/packages/transactions/tests/builder.test.ts @@ -2146,9 +2146,9 @@ test('Get contract map entry - no match', async () => { }); test('deserialize/serialize tenure change transaction', () => { - // test vector taken from https://github.com/hirosystems/stacks-encoding-native-js/blob/bba3528685912e30a86f1e35ed62573e43a2aa88/tests/tx-decode-3.0.test.ts#L15 + // test vector taken from https://github.com/hirosystems/stacks-encoding-native-js/blob/8e44b2d528191a3fdb7c32ee55bc1e0a25c04f14/tests/tx-decode-3.0.test.ts#L15 const txBytes = - '808000000004000f873150e9790e305b701aa8c7b3bcff9e31a5f9000000000000000000000000000000000001d367da530b92f4984f537f0b903c330eb5158262afa08d67cbbdea6c8e2ecae06008248ac147fc34101d3cc207b1b3e386e0f53732b5548bd5abe1570c2271340302000000000755c9861be5cff984a20ce6d99d4aa65941412889bdc665094136429b84f8c2ee00000001000000000000000000000000000000000000000000000000000279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980000000000000000000000000000000000000000000000000000000000000000'; + '808000000004001dc27eba0247f8cc9575e7d45e50a0bc7e72427d000000000000001d000000000000000000011dc72b6dfd9b36e414a2709e3b01eb5bbdd158f9bc77cd2ca6c3c8b0c803613e2189f6dacf709b34e8182e99d3a1af15812b75e59357d9c255c772695998665f010200000000076f2ff2c4517ab683bf2d588727f09603cc3e9328b9c500e21a939ead57c0560af8a3a132bd7d56566f2ff2c4517ab683bf2d588727f09603cc3e932828dcefb98f6b221eef731cabec7538314441c1e0ff06b44c22085d41aae447c1000000010014ff3cb19986645fd7e71282ad9fea07d540a60e0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798000000000000000000000000000000000000000000000000000000000000000000000000'; const transaction = deserializeTransaction(txBytes); expect(bytesToHex(transaction.serialize())).toEqual(txBytes);