Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions data/bookkeeping/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ type Genesis struct {

// Arbitrary genesis comment string - will be excluded from file if empty
Comment string `codec:"comment"`

// DevMode defines whether this network operates in a developer mode or not. Developer mode networks
// are a single node network, that operates without the agreement service being active. In liue of the
// agreement service, a new block is generated each time a node receives a transaction group. The
// default value for this field is "false", which makes this field empty from it's encoding, and
// therefore backward compatible.
DevMode bool `codec:"devmode"`
}

// LoadGenesisFromFile attempts to load a Genesis structure from a (presumably) genesis.json file.
Expand Down
53 changes: 38 additions & 15 deletions data/bookkeeping/msgp_gen.go

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

17 changes: 15 additions & 2 deletions data/pools/transactionPool.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,6 @@ func (pool *TransactionPool) OnNewBlock(block bookkeeping.Block, delta ledgercor
pool.mu.Lock()
defer pool.mu.Unlock()
defer pool.cond.Broadcast()

if pool.pendingBlockEvaluator == nil || block.Round() >= pool.pendingBlockEvaluator.Round() {
// Adjust the pool fee threshold. The rules are:
// - If there was less than one full block in the pool, reduce
Expand Down Expand Up @@ -865,7 +864,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim
return nil, fmt.Errorf("AssemblyBlock: encountered error for round %d: %v", round, pool.assemblyResults.err)
}
if pool.assemblyResults.roundStartedEvaluating > round {
// this scenario should not happen unless the txpool is receiving the new blocks via OnNewBlocks
// this scenario should not happen unless the txpool is receiving the new blocks via OnNewBlock
// with "jumps" between consecutive blocks ( which is why it's a warning )
// The "normal" usecase is evaluated on the top of the function.
pool.log.Warnf("AssembleBlock: requested round is behind transaction pool round %d < %d", round, pool.assemblyResults.roundStartedEvaluating)
Expand Down Expand Up @@ -900,3 +899,17 @@ func (pool *TransactionPool) assembleEmptyBlock(round basics.Round) (assembled *
}
return blockEval.GenerateBlock()
}

// AssembleDevModeBlock assemble a new block from the existing transaction pool. The pending evaluator is being
func (pool *TransactionPool) AssembleDevModeBlock() (assembled *ledger.ValidatedBlock, err error) {
pool.mu.Lock()
defer pool.mu.Unlock()

// drop the current block evaluator and start with a new one.
pool.recomputeBlockEvaluator(make(map[transactions.Txid]basics.Round), 0)

// The above was already pregenerating the entire block,
// so there won't be any waiting on this call.
assembled, err = pool.AssembleBlock(pool.pendingBlockEvaluator.Round(), time.Now().Add(config.ProposalAssemblyTime))
return
}
5 changes: 3 additions & 2 deletions gen/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ func GenerateGenesisFiles(genesisData GenesisData, consensus config.ConsensusPro
return fmt.Errorf("couldn't make output directory '%s': %v", outDir, err.Error())
}

return generateGenesisFiles(outDir, proto, consensusParams, genesisData.NetworkName, genesisData.VersionModifier, allocation, genesisData.FirstPartKeyRound, genesisData.LastPartKeyRound, genesisData.PartKeyDilution, genesisData.FeeSink, genesisData.RewardsPool, genesisData.Comment, verboseOut)
return generateGenesisFiles(outDir, proto, consensusParams, genesisData.NetworkName, genesisData.VersionModifier, allocation, genesisData.FirstPartKeyRound, genesisData.LastPartKeyRound, genesisData.PartKeyDilution, genesisData.FeeSink, genesisData.RewardsPool, genesisData.Comment, genesisData.DevMode, verboseOut)
}

func generateGenesisFiles(outDir string, protoVersion protocol.ConsensusVersion, protoParams config.ConsensusParams, netName string, schemaVersionModifier string,
allocation []genesisAllocation, firstWalletValid uint64, lastWalletValid uint64, partKeyDilution uint64, feeSink, rewardsPool basics.Address, comment string, verboseOut io.Writer) (err error) {
allocation []genesisAllocation, firstWalletValid uint64, lastWalletValid uint64, partKeyDilution uint64, feeSink, rewardsPool basics.Address, comment string, devmode bool, verboseOut io.Writer) (err error) {

genesisAddrs := make(map[string]basics.Address)
records := make(map[string]basics.AccountData)
Expand Down Expand Up @@ -352,6 +352,7 @@ func generateGenesisFiles(outDir string, protoVersion protocol.ConsensusVersion,
FeeSink: feeSink.String(),
RewardsPool: rewardsPool.String(),
Comment: comment,
DevMode: devmode,
}

for _, wallet := range allocation {
Expand Down
1 change: 1 addition & 0 deletions gen/walletData.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type GenesisData struct {
Wallets []WalletData
FeeSink basics.Address
RewardsPool basics.Address
DevMode bool
Comment string
}

Expand Down
15 changes: 11 additions & 4 deletions netdeploy/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ const maxGetRelayAddressRetry = 50

// NetworkCfg contains the persisted configuration of the deployed network
type NetworkCfg struct {
Name string
Name string `json:"Name,omitempty"`
// RelayDirs are directories where relays live (where we check for connection IP:Port)
// They are stored relative to root dir (e.g. "Primary")
RelayDirs []string
TemplateFile string // Template file used to create the network
RelayDirs []string `json:"RelayDirs,omitempty"`
TemplateFile string `json:"TemplateFile,omitempty"` // Template file used to create the network
}

// Network represents an instance of a deployed network
Expand Down Expand Up @@ -139,7 +139,14 @@ func (n Network) Name() string {

// PrimaryDataDir returns the primary data directory for the network
func (n Network) PrimaryDataDir() string {
return n.getNodeFullPath(n.cfg.RelayDirs[0])
if !n.gen.DevMode || len(n.cfg.RelayDirs) > 0 {
return n.getNodeFullPath(n.cfg.RelayDirs[0])
}
// for devmode, there should be only a single node, so pick it up.
for nodeName := range n.nodeDirs {
return n.getNodeFullPath(nodeName)
}
panic(fmt.Errorf("neither relay directories nor node directories are defined for the network"))
}

// NodeDataDirs returns an array of node data directories (not the relays)
Expand Down
47 changes: 43 additions & 4 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type AlgorandFullNode struct {
rootDir string
genesisID string
genesisHash crypto.Digest
devMode bool // is this node operates in a developer mode ? ( benign agreement, broadcasting transaction generates a new block )

log logging.Logger

Expand Down Expand Up @@ -164,6 +165,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
node.log = log.With("name", cfg.NetAddress)
node.genesisID = genesis.ID()
node.genesisHash = crypto.HashObj(genesis)
node.devMode = genesis.DevMode

if node.devMode {
cfg.DisableNetworking = true
}

// tie network, block fetcher, and agreement services together
p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network)
Expand Down Expand Up @@ -238,11 +244,16 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd

blockValidator := blockValidatorImpl{l: node.ledger, verificationPool: node.highPriorityCryptoVerificationPool}
agreementLedger := makeAgreementLedger(node.ledger, node.net)

var agreementClock timers.Clock
if node.devMode {
agreementClock = timers.MakeFrozenClock()
} else {
agreementClock = timers.MakeMonotonicClock(time.Now())
}
agreementParameters := agreement.Parameters{
Logger: log,
Accessor: crashAccess,
Clock: timers.MakeMonotonicClock(time.Now()),
Clock: agreementClock,
Local: node.config,
Network: gossip.WrapNetwork(node.net, log),
Ledger: agreementLedger,
Expand Down Expand Up @@ -462,10 +473,38 @@ func (node *AlgorandFullNode) Ledger() *data.Ledger {
return node.ledger
}

// writeDevmodeBlock generates a new block for a devmode, and write it to the ledger.
func (node *AlgorandFullNode) writeDevmodeBlock() (err error) {
var vb *ledger.ValidatedBlock
vb, err = node.transactionPool.AssembleDevModeBlock()
if err != nil || vb == nil {
return
}

// add the newly generated block to the ledger
err = node.ledger.AddValidatedBlock(*vb, agreement.Certificate{})
return err
}

// BroadcastSignedTxGroup broadcasts a transaction group that has already been signed.
func (node *AlgorandFullNode) BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error {
func (node *AlgorandFullNode) BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) (err error) {
// in developer mode, we need to take a lock, so that each new transaction group would truely
// render into a unique block.
if node.devMode {
node.mu.Lock()
defer func() {
// if we added the transaction successfully to the transaction pool, then
// attempt to generate a block and write it to the ledger.
if err == nil {
err = node.writeDevmodeBlock()
}
node.mu.Unlock()
}()
}

lastRound := node.ledger.Latest()
b, err := node.ledger.BlockHdr(lastRound)
var b bookkeeping.BlockHeader
b, err = node.ledger.BlockHdr(lastRound)
if err != nil {
node.log.Errorf("could not get block header from last round %v: %v", lastRound, err)
return err
Expand Down
Loading