From 918b3bec5e78e764e0aafb9fd3bfd564ea30f49b Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:15:10 -0500 Subject: [PATCH 1/2] Support custom consensus loading in go-algorand-sdk. --- protocol/config/config.go | 83 ++++++++++++++++++++++++++++++++++++ protocol/config/consensus.go | 37 ++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 protocol/config/config.go diff --git a/protocol/config/config.go b/protocol/config/config.go new file mode 100644 index 00000000..aa07bcb6 --- /dev/null +++ b/protocol/config/config.go @@ -0,0 +1,83 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" +) + +// ConfigurableConsensusProtocolsFilename defines a set of consensus protocols that +// are to be loaded from the data directory ( if present ), to override the +// built-in supported consensus protocols. +const ConfigurableConsensusProtocolsFilename = "consensus.json" + +// SaveConfigurableConsensus saves the configurable protocols file to the provided data directory. +// if the params contains zero protocols, the existing consensus.json file will be removed if exists. +func SaveConfigurableConsensus(dataDirectory string, params ConsensusProtocols) error { + consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename) + + if len(params) == 0 { + // we have no consensus params to write. In this case, just delete the existing file + // ( if any ) + err := os.Remove(consensusProtocolPath) + if os.IsNotExist(err) { + return nil + } + return err + } + encodedConsensusParams, err := json.Marshal(params) + if err != nil { + return err + } + err = os.WriteFile(consensusProtocolPath, encodedConsensusParams, 0644) + return err +} + +// PreloadConfigurableConsensusProtocols loads the configurable protocols from the data directory +// and merge it with a copy of the Consensus map. Then, it returns it to the caller. +func PreloadConfigurableConsensusProtocols(dataDirectory string) (ConsensusProtocols, error) { + consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename) + file, err := os.Open(consensusProtocolPath) + + if err != nil { + if os.IsNotExist(err) { + // this file is not required, only optional. if it's missing, no harm is done. + return Consensus, nil + } + return nil, err + } + defer file.Close() + + configurableConsensus := make(ConsensusProtocols) + + decoder := json.NewDecoder(file) + err = decoder.Decode(&configurableConsensus) + if err != nil { + return nil, err + } + return Consensus.Merge(configurableConsensus), nil +} + +// SetConfigurableConsensusProtocols sets the configurable protocols. +func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) ConsensusProtocols { + oldConsensus := Consensus + Consensus = newConsensus + // Set allocation limits + // checkSetAllocBounds not ported to sdk https://github.com/algorand/go-algorand/blob/e68b54e90cd9dc1b52c3a9df85e0aeb56e8206d5/config/consensus.go#L723 + // for _, p := range Consensus { + // checkSetAllocBounds(p) + // } + return oldConsensus +} + +// LoadConfigurableConsensusProtocols loads the configurable protocols from the data directory +func LoadConfigurableConsensusProtocols(dataDirectory string) error { + newConsensus, err := PreloadConfigurableConsensusProtocols(dataDirectory) + if err != nil { + return err + } + if newConsensus != nil { + SetConfigurableConsensusProtocols(newConsensus) + } + return nil +} diff --git a/protocol/config/consensus.go b/protocol/config/consensus.go index 32236ac7..009191c7 100644 --- a/protocol/config/consensus.go +++ b/protocol/config/consensus.go @@ -1,6 +1,7 @@ package config import ( + "maps" "time" "github.com/algorand/go-algorand-sdk/v2/protocol" @@ -669,6 +670,42 @@ type ConsensusProtocols map[protocol.ConsensusVersion]ConsensusParams // consensus protocol. var Consensus ConsensusProtocols +// DeepCopy creates a deep copy of a consensus protocols map. +func (cp ConsensusProtocols) DeepCopy() ConsensusProtocols { + staticConsensus := make(ConsensusProtocols) + for consensusVersion, consensusParams := range cp { + // recreate the ApprovedUpgrades map since we don't want to modify the original one. + consensusParams.ApprovedUpgrades = maps.Clone(consensusParams.ApprovedUpgrades) + staticConsensus[consensusVersion] = consensusParams + } + return staticConsensus +} + +// Merge merges a configurable consensus on top of the existing consensus protocol and return +// a new consensus protocol without modify any of the incoming structures. +func (cp ConsensusProtocols) Merge(configurableConsensus ConsensusProtocols) ConsensusProtocols { + staticConsensus := cp.DeepCopy() + + for consensusVersion, consensusParams := range configurableConsensus { + if consensusParams.ApprovedUpgrades == nil { + // if we were provided with an empty ConsensusParams, delete the existing reference to this consensus version + for cVer, cParam := range staticConsensus { + if cVer == consensusVersion { + delete(staticConsensus, cVer) + } else { + // delete upgrade to deleted version + delete(cParam.ApprovedUpgrades, consensusVersion) + } + } + } else { + // need to add/update entry + staticConsensus[consensusVersion] = consensusParams + } + } + + return staticConsensus +} + // initConsensusProtocols defines the consensus protocol values and how values change across different versions of the protocol. // // These are the only valid and tested consensus values and transitions. Other settings are not tested and may lead to unexpected behavior. From d156de26e9f76b18715b4d5f21431dd67b1d8d2d Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:41:51 -0500 Subject: [PATCH 2/2] Sync types from lastest export_sdk_types run. --- protocol/config/consensus.go | 8 ++++++++ types/block.go | 4 ++-- types/statedelta.go | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/protocol/config/consensus.go b/protocol/config/consensus.go index 009191c7..f9ad043d 100644 --- a/protocol/config/consensus.go +++ b/protocol/config/consensus.go @@ -559,6 +559,12 @@ type ConsensusParams struct { // available. This parameters can be removed and assumed true after the // first consensus release in which it is set true. EnableInnerClawbackWithoutSenderHolding bool + + // AppSizeUpdates allows application update transactions to change + // the extra-program-pages and global schema sizes. Since it enables newly + // legal transactions, this parameter can be removed and assumed true after + // the first consensus release in which it is set true. + AppSizeUpdates bool } // ProposerPayoutRules puts several related consensus parameters in one place. The same @@ -1368,6 +1374,8 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 13 // When moving this to a release, put a new higher LogicSigVersion here + vFuture.AppSizeUpdates = true + Consensus[protocol.ConsensusFuture] = vFuture // vAlphaX versions are an separate series of consensus parameters and versions for alphanet diff --git a/types/block.go b/types/block.go index 6967177c..5143c0f6 100644 --- a/types/block.go +++ b/types/block.go @@ -81,7 +81,7 @@ type BlockHeader struct { // Each block is associated with at most one active upgrade proposal // (a new version of the protocol). An upgrade proposal can be made // by a block proposer, as long as no other upgrade proposal is active. - // The upgrade proposal lasts for many rounds (UpgradeVoteRounds), and + // The upgrade proposal lasts for consensus.UpgradeVoteRounds, and // in each round, that round's block proposer votes to support (or not) // the proposed upgrade. // @@ -202,7 +202,7 @@ type UpgradeState struct { NextProtocol string `codec:"nextproto"` // NextProtocolApprovals is the number of approvals for the next protocol proposal. It is expressed in Round because it is a count of rounds. NextProtocolApprovals Round `codec:"nextyes"` - // NextProtocolVoteBefore specify the last voting round for the next protocol proposal. If there is no voting for + // NextProtocolVoteBefore specifies the last voting round for the next protocol proposal. If there is no voting for // an upgrade taking place, this would be zero. NextProtocolVoteBefore Round `codec:"nextbefore"` // NextProtocolSwitchOn specify the round number at which the next protocol would be adopted. If there is no upgrade taking place, diff --git a/types/statedelta.go b/types/statedelta.go index 00ad5742..8b26869f 100644 --- a/types/statedelta.go +++ b/types/statedelta.go @@ -61,6 +61,10 @@ type AppParams struct { StateSchemas ExtraProgramPages uint32 `codec:"epp"` Version uint64 `codec:"v"` + + // SizeSponsor, if non-zero, is the account that must hold MBR for + // extra program pages, and the global schema. + SizeSponsor Address `codec:"ss"` } // AppLocalState stores the LocalState associated with an application. It also