diff --git a/go.mod b/go.mod index 67aa334e..8bfcd6fe 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.24.1 require ( filippo.io/edwards25519 v1.1.0 github.com/blinklabs-io/ouroboros-mock v0.3.8 - github.com/blinklabs-io/plutigo v0.0.8 + github.com/blinklabs-io/plutigo v0.0.9 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/fxamacker/cbor/v2 v2.9.0 github.com/jinzhu/copier v0.4.0 diff --git a/go.sum b/go.sum index e4155627..a38fb35f 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blinklabs-io/ouroboros-mock v0.3.8 h1:+DAt2rx0ouZUxee5DBMgZq3I1+ZdxFSHG9g3tYl/FKU= github.com/blinklabs-io/ouroboros-mock v0.3.8/go.mod h1:UwQIf4KqZwO13P9d90fbi3UL/X7JaJfeEbqk+bEeFQA= -github.com/blinklabs-io/plutigo v0.0.8 h1:p0agbMDZ00skO1yx3Mg0S55phz/mCo8nYuixOaAAGJs= -github.com/blinklabs-io/plutigo v0.0.8/go.mod h1:L639Q8i2cSRuBhjgCHttPR0nnYwwsYVT4Btz7KpQjSw= +github.com/blinklabs-io/plutigo v0.0.9 h1:GPMJNfaiT6/wYs8MGZUfJYQ8tSHT9y+dpdvFprPOYYg= +github.com/blinklabs-io/plutigo v0.0.9/go.mod h1:L639Q8i2cSRuBhjgCHttPR0nnYwwsYVT4Btz7KpQjSw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= diff --git a/ledger/common/script/context.go b/ledger/common/script/context.go index 665252e1..09e48e3b 100644 --- a/ledger/common/script/context.go +++ b/ledger/common/script/context.go @@ -130,7 +130,7 @@ type TxInfoV3 struct { Redeemers KeyValuePairs[ScriptInfo, Redeemer] Data KeyValuePairs[lcommon.Blake2b256, data.PlutusData] Id lcommon.Blake2b256 - Votes KeyValuePairs[lcommon.Voter, KeyValuePairs[lcommon.GovActionId, lcommon.VotingProcedure]] + Votes KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]] ProposalProcedures []lcommon.ProposalProcedure CurrentTreasuryAmount Option[Coin] TreasuryDonation Option[PositiveCoin] @@ -160,8 +160,7 @@ func (t TxInfoV3) ToPlutusData() data.PlutusData { tmpRedeemers.ToPlutusData(), t.Data.ToPlutusData(), data.NewByteString(t.Id.Bytes()), - // TODO: votes - data.NewMap([][2]data.PlutusData{}), + t.Votes.ToPlutusData(), // TODO: proposal procedures toPlutusData([]any{}), t.CurrentTreasuryAmount.ToPlutusData(), @@ -188,6 +187,7 @@ func NewTxInfoV3FromTransaction( } inputs := sortInputs(tx.Inputs()) withdrawals := withdrawalsInfo(tx.Withdrawals()) + votes := votingInfo(tx.VotingProcedures()) redeemers := redeemersInfo( tx.Witnesses(), scriptPurposeBuilder( @@ -197,7 +197,7 @@ func NewTxInfoV3FromTransaction( tx.Certificates(), withdrawals, // TODO: proposal procedures - // TODO: votes + votes, ), ) tmpData := dataInfo(tx.Witnesses()) @@ -217,7 +217,7 @@ func NewTxInfoV3FromTransaction( Redeemers: redeemers, Data: tmpData, Id: tx.Hash(), - // TODO: Votes + Votes: votes, // TODO: ProposalProcedures } if amt := tx.CurrentTreasuryValue(); amt > 0 { @@ -454,6 +454,80 @@ func signatoriesInfo( return tmp } +func votingInfo( + votingProcedures lcommon.VotingProcedures, +) KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]] { + var ret KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]] + for voter, voterData := range votingProcedures { + voterPairs := make(KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure], 0, len(votingProcedures)) + for govActionId, votingProcedure := range voterData { + voterPairs = append( + voterPairs, + KeyValuePair[*lcommon.GovActionId, lcommon.VotingProcedure]{ + Key: govActionId, + Value: votingProcedure, + }, + ) + } + // Sort voter pairs by gov action ID + slices.SortFunc( + voterPairs, + func(a, b KeyValuePair[*lcommon.GovActionId, lcommon.VotingProcedure]) int { + // Compare TX ID + x := bytes.Compare(a.Key.TransactionId[:], b.Key.TransactionId[:]) + if x != 0 { + return x + } + // Compare index + if a.Key.GovActionIdx < b.Key.GovActionIdx { + return -1 + } else if a.Key.GovActionIdx > b.Key.GovActionIdx { + return 1 + } + return 0 + }, + ) + ret = append( + ret, + KeyValuePair[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]]{ + Key: voter, + Value: voterPairs, + }, + ) + } + // Sort by voter ID + slices.SortFunc( + ret, + func(a, b KeyValuePair[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]]) int { + voterTag := func(v *lcommon.Voter) int { + switch v.Type { + case lcommon.VoterTypeConstitutionalCommitteeHotScriptHash: + return 0 + case lcommon.VoterTypeConstitutionalCommitteeHotKeyHash: + return 1 + case lcommon.VoterTypeDRepScriptHash: + return 2 + case lcommon.VoterTypeDRepKeyHash: + return 3 + case lcommon.VoterTypeStakingPoolKeyHash: + return 4 + } + return -1 + } + tagA := voterTag(a.Key) + tagB := voterTag(b.Key) + if tagA == tagB { + return bytes.Compare(a.Key.Hash[:], b.Key.Hash[:]) + } + if tagA < tagB { + return -1 + } + return 1 + }, + ) + return ret +} + func certificatesToPlutusData( certificates []lcommon.Certificate, ) data.PlutusData { diff --git a/ledger/common/script/context_test.go b/ledger/common/script/context_test.go index 8379fd20..134d10d4 100644 --- a/ledger/common/script/context_test.go +++ b/ledger/common/script/context_test.go @@ -235,6 +235,18 @@ var scriptContextV3TestDefs = []struct { slotState: preprodSlotState, expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd87a9f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ffd8799fd8799fd87a9f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ffffffffa140a1401a000f4240d87b9fd87980ffd8799f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ffffffff809fd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd8799fd8799fd87a9f581c11111111111111111111111111111111111111111111111111111111ffffffffa140a1401a000f4240d87980d87a80ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a9f1a00261ec3181b03ffffffa140a1401a000f4240d87980d87a80ffd8799fd8799fd87a9f581c11111111111111111111111111111111111111111111111111111111ffd8799fd87a9f1a00261ec3181b03ffffffa140a1401a000f4240d87980d87a80ffff182aa080a1d87a9f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ff00d8799fd8799fd87980d87a80ffd8799fd87a9f1b000001739c890420ffd87980ffff9f581c00000000000000000000000000000000000000000000000000000000ffa2d87a9fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffffd87a81d87980d87b9fd87a9f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ffffd87980a0582040bee3b25a585a854fe73f3448a6f6b417fe669c813a1881e665971f34a9e984a080d87a80d8799f01ffffd87980d87b9fd87a9f581c04036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2ffffff", }, + { + name: "Voting", + txHex: "84a40081825820000000000000000000000000000000000000000000000000000000000000000000018002182a13a58200581c00000000000000000000000000000000000000000000000000000000a1825820999999999999999999999999999999999999999999999999999999999999999918988200827668747470733a2f2f61696b656e2d6c616e672e6f7267582000000000000000000000000000000000000000000000000000000000000000008202581c00000000000000000000000000000000000000000000000000000000a38258209999999999999999999999999999999999999999999999999999999999999999008202f68258208888888888888888888888888888888888888888888888888888888888888888018202f68258207777777777777777777777777777777777777777777777777777777777777777028202f68203581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1ea18258209999999999999999999999999999999999999999999999999999999999999999038200f68204581c00000000000000000000000000000000000000000000000000000000a18258209999999999999999999999999999999999999999999999999999999999999999048201f68201581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1ea18258209999999999999999999999999999999999999999999999999999999999999999018201f6a20582840402d87980821a000f42401a05f5e100840400d87981182a821a000f42401a05f5e1000781587d587b0101003232323232323225333333008001153330033370e900018029baa001153330073006375400224a66600894452615330054911856616c696461746f722072657475726e65642066616c73650013656002002002002002002153300249010b5f746d70303a20566f696400165734ae7155ceaab9e5573eae91f5f6", + inputsHex: "81825820000000000000000000000000000000000000000000000000000000000000000000", + outputsHex: "81a200581d6000000000000000000000000000000000000000000000000000000000011a000f4240", + redeemerTag: lcommon.RedeemerTagVoting, + redeemerIndex: 0, + // NOTE: this is slightly different than what's produced by the Aiken tests + // The Aiken test is "wrong" because the redeemer datum redefined in the test definition + // uses indef-length encoding while the in-TX redeemer datum uses def-length encoding + expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff8080182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a2d87d9fd8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffd87981182ad87d9fd87a9fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffd87980a05820e042ea3e95cf8156842e35639778441becaf3c862cb9459ee74b44aeeb88c673a5d8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999901ffd87a80d8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa1d8799f582099999999999999999999999999999999999999999999999999999999999999991898ffd87980d87a9fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999903ffd87980d87a9fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa3d8799f5820777777777777777777777777777777777777777777777777777777777777777702ffd87b80d8799f5820888888888888888888888888888888888888888888888888888888888888888801ffd87b80d8799f5820999999999999999999999999999999999999999999999999999999999999999900ffd87b80d87b9f581c00000000000000000000000000000000000000000000000000000000ffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999904ffd87a8080d87a80d87a80ffd87981182ad87d9fd8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffff", + }, } func TestScriptContextV3(t *testing.T) { diff --git a/ledger/common/script/purpose.go b/ledger/common/script/purpose.go index 5bc70b8a..d5e01c0f 100644 --- a/ledger/common/script/purpose.go +++ b/ledger/common/script/purpose.go @@ -154,17 +154,18 @@ type ScriptInfoVoting struct { func (ScriptInfoVoting) isScriptInfo() {} func (s ScriptInfoVoting) ScriptHash() lcommon.ScriptHash { - // TODO - return lcommon.ScriptHash{} + return lcommon.ScriptHash(s.Voter.Hash[:]) } func (s ScriptInfoVoting) ToPlutusData() data.PlutusData { - // TODO - return nil + return data.NewConstr( + 4, + s.Voter.ToPlutusData(), + ) } type ScriptInfoProposing struct { - Size uint64 + Index uint32 ProposalProcedure lcommon.ProposalProcedure } @@ -189,8 +190,8 @@ func scriptPurposeBuilder( mint lcommon.MultiAsset[lcommon.MultiAssetTypeMint], certificates []lcommon.Certificate, withdrawals KeyValuePairs[*lcommon.Address, uint64], + votes KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]], // TODO: proposal procedures - // TODO: votes ) toScriptPurposeFunc { return func(redeemerKey lcommon.RedeemerKey) ScriptInfo { // TODO: implement additional redeemer tags @@ -236,7 +237,9 @@ func scriptPurposeBuilder( }, } case lcommon.RedeemerTagVoting: - return nil + return ScriptInfoVoting{ + Voter: *(votes[redeemerKey.Index].Key), + } case lcommon.RedeemerTagProposing: return nil }